| membership_id | string, UUID | ✅ Yes | unique; generated via gen_random_uuid() on insert | — | Public stable identifier for this membership. Used in all API responses and external references. The internal pk (auto-increment integer) is never exposed. |
| firebase_id | string | ⚪ No | nullable; part of the composite index idx_memberships_firebase_workspace_deleted (firebase_id, workspace_pk, deleted_at) | — | Firebase Authentication UID of the user. Populated when the membership belongs to a Firebase-authed user. Used as the cross-Person identity key: a single Firebase user may have multiple Person rows (one per invited email) but should have exactly one active is_default membership across all of them. |
| membership_role | string (MembershipRole enum) | ⚪ No | nullable; constrained in application code to MEMBERSHIP_ROLES values; part of the composite index idx_memberships_workspace_role_deleted (workspace_pk, membership_role, deleted_at) | owner | admin | member | guest | The RBAC role granted to this user within the workspace. Determines which actions the user may perform. Role assignment happens at invitation acceptance, not at invite creation. Parent-workspace admin retention is automatic on workspace hierarchy changes. |
| status | string (MembershipStatus enum) | ✅ Yes | default: pending; non-nullable | pending | active | Lifecycle state of the membership. Newly created memberships (on invitation) start as pending; they transition to active upon the invitee accepting. Pending memberships confer no workspace access. |
| is_default | boolean | ✅ Yes | default: false; non-nullable. Business invariant: exactly one active, non-deleted membership per firebase_id must have is_default = true. Enforced by application logic and backfilled by Migration20260424100000. | true | false | Flags which workspace is the user’s default landing workspace. Used to redirect the user after login when no explicit workspace is specified. Exactly one active membership per firebase_id should be marked true; zero or multiple defaults indicate a corrupted state (see Migration20260424100000_backfill_membership_default_flag). |
| invite_token | string, UUID | ⚪ No | nullable; unique (partial — uniqueness enforced across non-null values only) | — | One-time token distributed in the invitation email. Present while the membership is in pending status; nulled out (or the row is reused) upon acceptance. Serves as the authentication credential for the invitation acceptance flow. Re-sending an invite to the same email reuses the existing pending membership row rather than creating a new one. |
| created_at | Date, 🔒 system | ✅ Yes | set by @Property onCreate hook; non-nullable | — | Timestamp when the membership record was created (i.e., when the invitation was issued). |
| updated_at | Date, 🔒 system | ⚪ No | set by @Property onCreate and onUpdate hooks; nullable in the DB column definition | — | Timestamp of the last mutation to this row (e.g., status transition from pending to active, role change). Auto-managed by the ORM lifecycle hook. |
| deleted_at | Date | ⚪ No | nullable; part of composite indexes for firebase_id and workspace+role hot-path lookups. Soft-delete sentinel: when non-null the membership is revoked. | — | Soft-delete timestamp. A non-null value means the membership has been revoked. The row is retained for audit history. All active-membership queries filter deleted_at IS NULL. |