|
| 1 | +--- |
| 2 | +name: constructive-membership-types |
| 3 | +description: "Membership types and dynamic entity provisioning — how to create custom entity types (channels, departments, teams) via the ORM, CLI, or blueprint definitions. Covers the entity hierarchy, permissions per entity type, and the provisioning lifecycle." |
| 4 | +metadata: |
| 5 | + author: constructive-io |
| 6 | + version: "1.0.0" |
| 7 | +--- |
| 8 | + |
| 9 | +# Membership Types & Dynamic Entity Provisioning |
| 10 | + |
| 11 | +Constructive has a hierarchical entity type system. Every scope of membership — app, org, channel, department, team — is a **membership type** with its own entity table, permissions, memberships, and security policies. |
| 12 | + |
| 13 | +Types 1 (app) and 2 (org) are built-in. Types 3+ are **dynamic** — you define them at runtime via the ORM, CLI, or blueprint definitions. |
| 14 | + |
| 15 | +Related skills: |
| 16 | +- **Blueprints:** `constructive` → [blueprints.md](../constructive/references/blueprints.md) — how `constructBlueprint()` works |
| 17 | +- **Blueprint definition format:** `constructive` → [blueprint-definition-format.md](../constructive/references/blueprint-definition-format.md) — table/relation/policy JSONB spec |
| 18 | +- **Safegres (security):** `constructive-safegres` — Authz* policy types for RLS |
| 19 | +- **SQL-level provisioning:** `entity-types-and-provisioning` skill in `constructive-db` |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +## Core Concepts |
| 24 | + |
| 25 | +### Entity Type Hierarchy |
| 26 | + |
| 27 | +| Type ID | Name | Prefix | Entity Table | Created By | |
| 28 | +|---------|------|--------|-------------|------------| |
| 29 | +| 1 | App Member | `app` | `users` | Built-in | |
| 30 | +| 2 | Organization Member | `org` | `users` (scoped) | Built-in | |
| 31 | +| 3+ | Dynamic | varies | auto-created | You provision these | |
| 32 | + |
| 33 | +Every entity type gets: |
| 34 | +- An **entity table** (e.g. `channels`, `departments`) |
| 35 | +- A **permissions module** with bitmask-based permissions |
| 36 | +- A **memberships module** for tracking who belongs to what |
| 37 | +- **RLS security policies** on all tables |
| 38 | +- Optional modules: limits, profiles, levels, invites |
| 39 | + |
| 40 | +### Permission Model |
| 41 | + |
| 42 | +Each level has a standard set of permissions. The `create_entity` permission means **"create the next level down"**: |
| 43 | + |
| 44 | +| Level | `create_entity` description | What it creates | |
| 45 | +|-------|---------------------------|-----------------| |
| 46 | +| App (type=1) | "Create organization entities." | Organizations | |
| 47 | +| Org (type=2) | "Create child entities." | Channels, departments, etc. | |
| 48 | +| Dynamic (type≥3) | "Create sub-entities." | Nested entity types | |
| 49 | + |
| 50 | +Other standard permissions: `admin_members`, `create_invites`, `admin_invites`, `admin_limits`, `admin_permissions`, `admin_entity`. |
| 51 | + |
| 52 | +### Parent-Child Relationships |
| 53 | + |
| 54 | +Every dynamic entity type has a **parent type**. The parent defaults to `org` (type=2), but can be any previously-provisioned type: |
| 55 | + |
| 56 | +``` |
| 57 | +app (1) |
| 58 | + └── org (2) |
| 59 | + ├── channel (3) ← parent_entity = 'org' |
| 60 | + ├── department (4) ← parent_entity = 'org' |
| 61 | + │ └── team (5) ← parent_entity = 'department' |
| 62 | + └── ... |
| 63 | +``` |
| 64 | + |
| 65 | +Nested types must be provisioned **after** their parent type. |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +## Three Ways to Provision Entity Types |
| 70 | + |
| 71 | +### 1. Blueprint Definition (Recommended) |
| 72 | + |
| 73 | +Add `membership_types` to the blueprint `definition` JSONB. These are processed in **Phase 0** — before tables and relations — so blueprint tables can reference the entity tables they create. |
| 74 | + |
| 75 | +See [blueprint-membership-types.md](./references/blueprint-membership-types.md) for the full spec and examples. |
| 76 | + |
| 77 | +### 2. ORM / GraphQL Mutation |
| 78 | + |
| 79 | +Use the `entityTypeProvision` table for direct provisioning outside of blueprints. |
| 80 | + |
| 81 | +See [orm-provisioning.md](./references/orm-provisioning.md) for ORM examples. |
| 82 | + |
| 83 | +### 3. CLI |
| 84 | + |
| 85 | +```bash |
| 86 | +# Direct entity type provision (inserts into entity_type_provision trigger table) |
| 87 | +constructive public:entity-type-provision create \ |
| 88 | + --databaseId <UUID> \ |
| 89 | + --name "Channel Member" \ |
| 90 | + --prefix "channel" \ |
| 91 | + --description "Membership to a channel." \ |
| 92 | + --parentEntity "org" \ |
| 93 | + --isVisible true \ |
| 94 | + --hasLimits false \ |
| 95 | + --hasProfiles false \ |
| 96 | + --hasLevels false \ |
| 97 | + --skipEntityPolicies false |
| 98 | +``` |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +## What Gets Created |
| 103 | + |
| 104 | +When you provision a new entity type (e.g. prefix=`channel`), the system creates: |
| 105 | + |
| 106 | +### Tables |
| 107 | +- `channels` — Entity table (with `id`, `name`, `owner_id`, `created_at`, `updated_at`) |
| 108 | +- `channel_permissions` — Permission bitmasks per member |
| 109 | +- `channel_permission_defaults` — Default permission values |
| 110 | +- `channel_limits` — Rate limits per member (if `has_limits`) |
| 111 | +- `channel_limit_defaults` — Default limit values (if `has_limits`) |
| 112 | +- `channel_members` — Member list (user_id + entity_id) |
| 113 | +- `channel_memberships` — Membership state (active, suspended, etc.) |
| 114 | +- `channel_membership_defaults` — Default membership values |
| 115 | +- `channel_grants` / `channel_admin_grants` / `channel_owner_grants` — Computed grants |
| 116 | +- `channel_acl` — Access control list |
| 117 | + |
| 118 | +### Modules Registered |
| 119 | +- `permissions_module:channel` |
| 120 | +- `memberships_module:channel` |
| 121 | +- `limits_module:channel` (if `has_limits`) |
| 122 | +- `invites_module:channel` (auto-provisioned when `emails_module` exists) |
| 123 | + |
| 124 | +### Optional Modules |
| 125 | +- `profiles_module:channel` (if `has_profiles`) — Named permission roles |
| 126 | +- `levels_module:channel` (if `has_levels`) — Gamification/achievements |
| 127 | + |
| 128 | +--- |
| 129 | + |
| 130 | +## Querying Membership Types |
| 131 | + |
| 132 | +### List all types |
| 133 | + |
| 134 | +```typescript |
| 135 | +const types = await db.membershipType.findMany({ |
| 136 | + select: { |
| 137 | + id: true, |
| 138 | + name: true, |
| 139 | + prefix: true, |
| 140 | + description: true, |
| 141 | + parentMembershipType: true, |
| 142 | + hasLimits: true, |
| 143 | + hasProfiles: true, |
| 144 | + hasLevels: true, |
| 145 | + } |
| 146 | +}).execute(); |
| 147 | +// Returns: [{ id: 1, name: 'App Member', prefix: 'app', ... }, ...] |
| 148 | +``` |
| 149 | + |
| 150 | +### Find a specific type by prefix |
| 151 | + |
| 152 | +```typescript |
| 153 | +const channelType = await db.membershipType.findMany({ |
| 154 | + where: { prefix: { equalTo: 'channel' } }, |
| 155 | + select: { id: true, name: true } |
| 156 | +}).execute(); |
| 157 | +``` |
| 158 | + |
| 159 | +### CLI |
| 160 | + |
| 161 | +```bash |
| 162 | +constructive public:membership-type list --select id,name,prefix,parentMembershipType |
| 163 | +constructive public:membership-type find --where.prefix channel --select id,name |
| 164 | +``` |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## Querying Membership Types Module |
| 169 | + |
| 170 | +The `membershipTypesModule` tracks which databases have the membership types infrastructure installed: |
| 171 | + |
| 172 | +```typescript |
| 173 | +const modules = await db.membershipTypesModule.findMany({ |
| 174 | + where: { databaseId: { equalTo: dbId } }, |
| 175 | + select: { id: true, tableName: true } |
| 176 | +}).execute(); |
| 177 | +``` |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## Cross-References |
| 182 | + |
| 183 | +- **Blueprint definition format:** [blueprint-definition-format.md](../constructive/references/blueprint-definition-format.md) — `membership_types` is a top-level key alongside `tables`, `relations`, etc. |
| 184 | +- **ORM provisioning examples:** [orm-provisioning.md](./references/orm-provisioning.md) |
| 185 | +- **Blueprint membership_types spec:** [blueprint-membership-types.md](./references/blueprint-membership-types.md) |
| 186 | +- **SQL-level detail:** `entity-types-and-provisioning` skill in `constructive-db` repo |
0 commit comments