1. Core Entities
Membership
Represents the link between a User and a Tenant.
- Attributes:
id: UUIDuserId: UUIDtenantId: UUIDroleId: UUIDisOwner: Boolean (Immutable flag for the tenant creator, bypasses role checks)joinedAt: Timestampstatus:ACTIVE|SUSPENDED
Invitation
A request for a user (existing or new) to join a Tenant.
- Attributes:
id: NanoID (Publicly shareable token)tenantId: UUIDemail: String (The intended recipient)roleId: UUID (The role they will get)invitedBy: UUID (UserId of sender)expiresAt: Timestamp (default 7 days)redeemedAt: Timestamp | NullredeemedBy: UUID | Nullstatus:PENDING|ACCEPTED|EXPIRED|REVOKED
Role
A collection of permissions.
- Types:
- System Roles: Hardcoded, available to all tenants (e.g.,
Viewer,Editor,Admin). Cannot be modified. - Custom Roles: Created by Tenant Admins. Scoped to
tenantId.
- System Roles: Hardcoded, available to all tenants (e.g.,
- Attributes:
id: UUIDtenantId: UUID | Null (Null = System Role)name: Stringpermissions: Set(JSON)
Permission
A granular capability string.
- Format:
resource:action - Examples:
blog:posts.createblog:posts.publish(Separate from create!)team:members.inviteteam:members.removebilling:invoices.read
2. Relationships
3. Business Logic & Invariants
Invitation Lifecycle (State Machine)
- Draft: Created.
- Sent: Email dispatched. Status ->
PENDING. - Click: User validates token. Checks
expiresAt. - Accept:
- IF Email matches logged-in user OR Invitation allows "Any Email" (configurable? No, safest is Strict Match).
- THEN: Create
Membership, Status ->ACCEPTED.
- Expire:
CurrentTime > expiresAt. Status ->EXPIRED. - Revoke: Admin cancels invite. Status ->
REVOKED. Token invalid.
Role Logic
- Immutable Owners: The
Ownerrole cannot be deleted or edited. There must always be at least one Owner per tenant (usually). - Self-Destruct Prevention: An Admin cannot remove their own
team:managepermission (or allow them to, but warn). - Hierarchy: A user with
team:managecannot assign a role "higher" than their own (needs a simplified "Role Level" integer to enforce, e.g., Owner=100, Admin=50, Member=10).
Security Constraints
- Cross-Tenant Isolation: A Custom Role from Tenant A cannot be assigned to a user in Tenant B.
- Orphan Prevention: A Tenant must validly exist to invite members.