Build a role from capabilities, and tune a membership tier
Create a group, tick the features it may use, map its doors, and set what a membership tier is actually worth — all without a code change.
Owned by Admin · 23 steps · about 24 minutes
Why this exists
Roles used to be a fixed list in Python. If the club hired a chef, or wanted bar staff who can charge a house-credit tab but cannot scan tickets, that was a code change and a deploy. Now groups, capabilities, the implication graph and the membership-tier benefits are all rows. You create a group, tick the features it may use, map its door zones and assign people to it — and nothing gets rebuilt.
What a capability is
One auditable thing a user may do, named module-noun-verb. There are a hundred and sixty-odd of them across eighteen modules — /admin/capabilities, step 1 below, prints the exact total, and how many are dangerous and sensitive, every time you open it (162 across 18 on 2026-08-29, when this sentence last said 157 across 17) — and each carries a risk level that drives how the admin UI presents it: dangerous means it moves money, changes permissions, destroys data or bypasses a control; sensitive means it reads personal data or credentials; everything else is normal. Some are marked as acting only on the caller's own rows, which is how "read your own orders" and "read every customer's orders" stay two different things.
Capabilities are never auto-deleted. A slug that disappears from the catalogue is reported as stale so a human decides, because silently dropping a permission row is how an install quietly loses a control it thought it had.
What did NOT change
Every existing permission check kept its signature and its semantics. Only the source of the inheritance edges moved from a hardcoded dictionary into a table. That claim is not a hope — it is pinned by a committed route-by-role matrix that the test suite asserts against, so a group edit that would silently open a route to a role that never had it shows up as a diff somebody has to approve.
Membership tiers are just groups with a rank
A tier is a group with a rank from one to five. Member and VIP member are ranks one and two, seeded at values that reproduce exactly the behaviour the platform had before tiers existed — every legacy value is the do-nothing value. Your effective tier is the highest-ranked unscoped tier you hold, and there is deliberately no exclusivity rule, because a VIP already holds both member and VIP member today and breaking that would break everything downstream.
Five benefits are editable: how many minutes of early access the tier gets before a sale opens, how many basis points comes off the resale fee, the house-credit tab limit, how many guest-list seats the tier may self-allocate per event, and whether member-only drops are visible at all.
The anti-lockout design, which you should read before you experiment
Four guards, and three of them are database triggers — so the universal admin grid and a raw shell obey them too, not just this UI. System groups cannot be deleted, their slugs and kinds cannot be changed, and core capabilities cannot be removed from them. On top of that, a group marked superuser resolves every capability regardless of what its rows say, so an emptied permission table cannot lock the administrators out. And if it somehow still goes wrong, there is an offline repair command that grants admin back from a shell.
Belt and braces on top: applying a capability set runs a lockout analysis first, and you can ask for it as a dry run before you commit to anything.
Before you start
- An admin session. Group administration is admin-only on a default install.
- A clear idea of the job the new role does. Building the group is ten minutes; deciding what it may do is the work.
- The seed already ships five non-system roles to read as worked examples: artist and promoter (event-scoped), and chef, security and bar (not scoped).
Practise with
| Persona | Password | Note | |
|---|---|---|---|
| admin | admin@club.test | admin123 | the only role holding group administration on a default install |
| member | member@club.test | member123 | rank 1 tier; the person whose benefits change when you edit one |
Steps 1–17 — Admin
their manual →-
1Start at the capability reference, not at the group form. Filter by module and by risk and read what actually exists before you decide what your new role needs.Expected result The live total in the header — capabilities, how many dangerous, how many sensitive — then the catalogue grouped by module, each with a label, a risk level and a sentence of description.Watch out for Read the dangerous ones deliberately. They are the ones that move money, change permissions, destroy data or bypass a control — and a role that holds one of them by accident is the failure this whole feature exists to make visible.
-
2Open the group list. Each row shows its kind — role, staff, partner, membership tier or the employee pseudo-group — and a tier badge where it has a rank.Expected result The system groups plus the seeded non-system ones.Watch out for Open security and bar before you build anything. Security is door staff minus the guest-list door override; bar can charge a house-credit tab and cannot scan tickets at all. Both are small, real examples of the point of this feature.
-
3Create the group. The slug is lowercase letters, digits and underscores; pick the kind; decide whether it is event-scoped — granted per event, like artist and promoter — or held outright, like chef.Expected result The form, with the free tier ranks listed if you choose a membership tier.Watch out for Scoping is the decision that is painful to change later, because it changes what a grant even means. 'Sound engineer on Saturday' is scoped; 'sound engineer' is not.
-
4Save it. The group is immediately grantable — nothing else has to know it exists.Expected result A redirect onto the new group's own page.Watch out for A slug that was used before is refused as retired rather than reused. Recycling a slug would silently attach a new role to every audit row the old one ever wrote.
-
5Work through the tabs: capabilities, zones, tier benefits (tiers only), members, implications.Expected result One page per concern, with the group's audit history underneath.Watch out for Capabilities decide which buttons exist. Zones decide which doors open. They are different questions and a role frequently needs one without the other — a bookkeeper needs no door at all.
-
6Tick the capabilities and submit it as a dry run first. You get back what would be added, what would be removed, and a lockout analysis.Expected result A diff and a verdict, with nothing written.Watch out for Always dry-run a removal. The analysis answers the only question that matters — after this change, who can still administer groups — and it answers it about real users rather than about your intentions.
-
7Apply the set for real once the dry run reads the way you expect.Expected result The additions and removals applied, audited, and live on the next request.Watch out for This is a full replacement, not a merge: whatever you did not tick is removed. Submitting a partially-loaded form is the classic way to strip a role of half its permissions without noticing.
-
8Map the group's door zones on the zones tab.Expected result The zone list replaced, audited, and a pass refresh queued for everybody already holding the group.Watch out for This is the fix for a long-standing gap: zone changes now fan out to existing holders instead of only affecting cards issued afterwards. Otherwise a terminated employee's membership card would keep opening the zone it was minted with.
-
9Declare what this group also grants. Holding it then resolves the capabilities of everything it implies.Expected result The implication edges replaced; cycles refused.Watch out for Implication is where a small role quietly becomes a large one. Two hops is usually one too many — prefer ticking the capability you meant over inheriting a group that happens to contain it.
-
10Before you grant it to a person, simulate: given this set of grants, what does the graph resolve to?Expected result The effective groups and capabilities for a hypothetical user.Watch out for Simulating is free and reversing a grant is not — a revoked grant is still an audit row that somebody will ask about.
-
11Open the person you are granting it to and read what they already hold, including anything scoped to a single event.Expected result Their live grants, their sessions and their audit timeline.Watch out for Somebody who already holds a broader group gains nothing from your new one, and the new grant will then mislead the next person who reads their profile.
-
12Grant the group, with a reason. Event-scoped groups require the event.Expected result The grant recorded, effective on the user's very next request.Watch out for The reason is required and it is the only place the why ever lives. 'temp' is not a reason; 'covering Saturday for K' is.
-
13Verify from the other end: ask what this user can now do, with provenance for each capability.Expected result Every capability they hold and which group supplied it.Watch out for Provenance is what makes an over-permissioned account fixable. 'They can refund orders' is a complaint; 'they can refund orders because they are in bar, which implies a group that holds it' is a fix.
-
14Now build a membership tier: same form, kind set to membership tier, and a rank. Leave the rank on auto to take the first free slot.Expected result A tier occupying one of five slots, with an empty benefit row created for it.Watch out for There are five slots and the ranks are unique. Ranks one and two are already member and VIP member, so a tier you want between them needs the reorder step below rather than a clever choice of number now.
-
15Open the tier's benefits tab and set the five values: early-access minutes before a sale opens, basis points off the resale fee, the house-credit tab limit, the comp allowance per event, and whether member-only drops are visible.Expected result A form with each field explained and its do-nothing value named.Watch out for The tab limit has three meanings: a number is that limit, blank falls back to the platform default, and zero means no tab at all. Blank and zero are not the same answer and the difference is somebody's bar bill.
-
16Save the benefits — dry-run first if you are changing an existing tier rather than filling in a new one.Expected result The changed fields listed, with a count of passes that would be reissued.Watch out for None of the five benefits is inside a signed door credential, so a benefit edit that does not touch zones queues no pass refresh. If it reports passes queued, you changed something you did not think you changed.
-
17Reorder the tiers when the ladder changes shape.Expected result Ranks reassigned in the order you gave.Watch out for Rank is what 'highest tier' means, and there is no exclusivity rule — somebody holding two tiers resolves to the higher one. Reordering therefore silently re-answers 'what tier is this person' for everybody holding more than one.
Steps 18–19 — Member
their manual →Is the person a tier is for — and the only one who can confirm the benefit actually landed.
-
18As the member, open your account page and read the groups you hold.Expected result Your grants, including your tier.Watch out for A VIP holds member and VIP member. That is correct and always has been — the higher rank is what decides your benefits, not the number of rows.
-
19Check the benefit actually landed: is the member-only drop visible, and does the early-access window open when it should?Expected result The catalogue reflecting your tier's benefits.Watch out for This is the only real confirmation. An admin reading the benefits form sees what was configured; a member reading the catalogue sees what was delivered, and those are different claims.
Steps 20–23 — Admin
their manual →-
20Export the group-by-capability matrix — as CSV when you want to diff it against last month's.Expected result Every capability against every group, in one grid.Watch out for Diffing this after a round of edits is the cheapest permission review that exists. A row that gained a dangerous capability nobody remembers discussing is exactly what the grid is for.
-
21Read the resolved graph when behaviour surprises you, and check the stale capability list while you are there.Expected result Groups, edges, superuser flags and any slugs no longer in the catalogue.Watch out for A stale slug is not cleaned up automatically, on purpose. Deciding whether a permission that no longer exists in code should be removed from every group is a human decision with consequences.
-
22After a zone change, watch the pass refresh queue drain. It is worked by a scheduled handler in batches.Expected result Queued entries clearing as cards are reissued.Watch out for Until it drains, existing holders are still carrying their old zone list. On a night when you have just revoked somebody's access, that lag is the thing to check rather than assume.
-
23Finally, know the guards you cannot switch off. System groups are undeletable, their slugs and kinds immutable, and their core capabilities un-removable — enforced by database triggers, so the universal data grid obeys them too. All five policy tables are registered read-only there. And there is an offline repair command that grants admin back from a shell.Expected result A refusal from the database itself, not merely from this UI.Watch out for Those guards exist because this feature's failure mode is not a bad page — it is nobody being able to sign in and fix it.