logo logo
Design: Database: ULID-Role meta

Database ULID-Role (ULID is member's user dbid as a ulid)
Purpose Track a member's role and engagement databases.
Cardinality exactly one ULID-Role database per member
exactly one RoleItem per member
Source Code
Items src/ts/dbitems/ULID-Role.ts
Zod Models
item RoleItem
item.item RoleRecord
itemId roledbid - dbid of member's Role database
item: RoleRecord
     kind 'role'
     mnum number of member in the Members db (first is 1)
     role 'host', 'guest' or 'removed'
     roledbids object mapping member number to dbid of ULID-Role database for member
(guest only has own)
         members dbid of Members database
         user dbid of member's User database.
The ULID-Role database name uses the ULID form of user as a prefix.
     partnerdbids object mapping member number to PartnerIds object for member
(guest only has own)
bundles dbid of ULID-Bundles database for bundles and share credentials shared with guest


Each member in an engagement has a Role which identifies the member's role (host, guest or removed) and the dbids of databases containing engagement data.

The name of the database is ULID-Role where ULID is the member's User database dbid represented as a ulid. For example 2EAJ7WP8YW9RFAKFAZAS2C2Z04-Role is the name of the ULID-Role database for the user whose User database dbid is 4e548fcb-23dc-4e1e-a9bd-5f5644c17c04.

The ULID-Role database represents the 'root' of the tree of databases a member sees in an engagement.

         one per                                     one per
         member                                      engagement
AppID ╶╶╶╶╶╶╶╶▶ ULID-Role ╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶▶ Members
 +              database                                          database
RoleID          (1 item)                                          (1 per member)
                   ╵             shared with all members             ╵
                   ╵                                                 ╵
                   ╵  member's profile                 all profiles  ╵
                   ╵  member's topics                  all topics    ╵
                   └╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶▶ User      ◀╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶┘
                   ╵                      database
                Partner databases (only shared between host and guest)
                   └╶╶╶╶╶╶╶╶╶▶ ULID-Bundles   owned by host

The member's ULID-Role database plays an important part in the authentication process. The 'AppIdUlid' and 'Role dbulid' from the engagement URL identify the Members database which determines the participants in the engagement.


It may not be obvious why this table is necessary given that Userbase provides the getDatabases ↗︎ API. The issue is nothing in Userbase prohibits any user from sharing a database with any other user in the same AppID. The Userbase SDK allows users to create whatever databases they like so applications should not blindly process databases shared with a user.

Securepub uses the ULID-Role database to ensure unauthorized Userbase users in the same AppID cannot spoof members by sharing databases which appear as legitimate databases in an engagement. Such attempts won't work because Securepub only recognizes databases "reachable" from the member's ULID-Role database.

Ownership and Access

The host of the engagement is the owner of all ULID-Role databases.

The host's ULID-Role database is created when the engagement is created.

Other ULID-Role databases are created when the Host adds members to the engagement.

Zod models

The following Zod models show how the Userbase items are represented in memory.

Note that in addition to the values specified by the Userbase Item interface the kind and database attributes are added during deserialization.

export type  RoleItem = z.infer<typeof RoleItem>;
export const RoleItem = z.object({
  kind:                 z.literal('roleitem'),
  itemId:               z.string(),
  item:                 RoleRecord,
  updatedBy:            z.custom<Attribution>(),
  database:             z.custom<Database>()
export type  RoleRecord = z.infer<typeof RoleRecord>;
export const RoleRecord = z.object({
  kind:                 z.literal('role'),
  mnum:                 z.number(),
  role:                 z.union([
  roledbids:            z.record(z.string(), z.string().uuid()),
  publicdbids:          PublicIds,
  partnerdbids:         z.record(z.string(), PartnerIds)
export type  PublicIds = z.infer<typeof PublicIds>;
export const PublicIds = z.object({
  members:              z.string().uuid(),
  user:                 z.string().uuid(),
export type  PartnerIds = z.infer<typeof PartnerIds>;
export const PartnerIds = z.object({
  bundles:              z.string().uuid(),
  activity:             z.string().uuid()