ADR-021: Team invitations
Status: accepted 7 October 2026. Item 5 of the onboarding list: a shop had no way to add its staff. Roles and permissions existed (ADR-003, STAB-06) without a page to use them.
Decision
Section titled “Decision”-
Settings → Team (
/control/settings/team) lists members with their roles and open invitations.- It is shown to those with
users.invite(manager and above). - Changing a role needs
users.manage, and removing needsusers.remove(both admin and above).
- It is shown to those with
-
Only below one’s own role (
WorkspaceRole::level()andassignable()):Role Can give Owner admin, manager, staff, viewer Admin manager, staff, viewer Manager staff, viewer (invite only) Nobody changes or removes the owner, someone at or above their own level, or themselves. Ownership transfer stays a separate owner-only action (ADR-003).
-
Invitations (
workspace_invitations):- Addressed to an email, with a role.
- The link carries a 48-character random token, stored only as its SHA-256 hash.
- Valid for 7 days and used once; it can be revoked.
- Inviting the same address again replaces the open invitation.
- At most 50 can be open per shop, and sending is rate-limited.
- The email is queued and names the shop, the inviter and the role.
-
Accepting happens on the root domain at
/invitations/{token}:- A guest signs in or creates an account (the address is filled in) and comes back to the link: sign-in, sign-up and Google sign-in all honour the intended URL.
- Only the account with the invited address can join; another account is told whose invitation it is.
- Joining adds the membership with the role, makes the shop the current one, records who invited whom, and opens the shop’s control panel.
- The invitation reached that mailbox, so joining also confirms the account’s address.
-
Removing a member detaches them. Their current shop moves to another membership or none, and their next request to the shop is refused by the membership middleware.
Consequences
Section titled “Consequences”- Staff no longer need the owner’s password, and each person’s actions are attributed to them.
- Onboarding reminders (ADR-020) never ask members of someone else’s shop to create a shop.
- A member removed while signed in is refused on their next request; there is no session revocation beyond that.
- Changing a role does not email the member.