Skip to main content

Workspace and member tables

Eight workspace-domain tables. Before reading, remember one rule: business tables belong to a workspace via a soft-reference workspace_id column ('' = personal data) with no foreign key; the tables here own "the workspace itself, membership, policy, audit, and domain enrollment" (notation per the overview).

workspaces — workspace roots

6 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)ws_ + 12 hex chars
nameTEXTnoworkspace name
owner_idTEXTnoFK→users(id), CASCADE. The single source of owner-exclusive authority — the member role admin is not ownership
invite_tokenTEXTno (UNIQUE)legacy 192-bit capability token: kept for schema compatibility but no longer accepted by the application (see workspace_invites)
deletingINTEGERno0durable deletion fence preventing creations from racing a multi-step teardown; reset when a recoverable teardown fails
created_atINTEGER → BIGINTnonow()creation time
  • Index: idx_workspaces_owner(owner_id); invite_token UNIQUE.

workspace_members — membership and granular capabilities

17 columns.

ColumnTypeNullableDefaultNotes
workspace_idTEXTno (PK part)FK→workspaces(id), CASCADE
user_idTEXTno (PK part)FK→users(id), CASCADE
roleTEXTno'member'admin / member / guest. Migration collapses legacy owneradmin and any unknown value → guest (fail-closed) (store.go:511-523)
can_create_projectsINTEGERno1create projects
can_private_conversationsINTEGERno1create private (self-only) conversations
can_create_skills_promptsINTEGERno1legacy combined skill+prompt creation switch (kept; the granular columns supersede it)
can_create_promptsINTEGERno1create shared prompts (backfilled from the combined switch on first split)
can_create_skillsINTEGERno1create shared skills (same backfill)
can_create_mcpINTEGERno1create shared MCP servers (same backfill)
can_use_promptsINTEGERno1use prompts
can_use_skillsINTEGERno1use skills
can_use_mcpINTEGERno1use MCP
can_create_kbINTEGERno1create knowledge bases
can_add_kb_filesINTEGERno1add files to libraries
can_delete_kb_contentINTEGERno1delete library content
can_delete_conversationsINTEGERno1delete conversations
joined_atINTEGER → BIGINTnonow()join time
  • PK: composite (workspace_id, user_id). Index: idx_ws_members_user(user_id).
  • Notes: all 13 can_* columns are 0/1 INTEGER, permissive by default (1); restrictions are written explicitly by admins.

workspace_kb_member_permissions — per-library overrides

Member permissions scoped to one knowledge base. 5 columns.

ColumnTypeNullableDefaultNotes
kb_idTEXTno (PK part)FK→knowledge_bases(id), CASCADE
user_idTEXTno (PK part)FK→users(id), CASCADE
can_add_filesINTEGERno1add files to this library
can_delete_contentINTEGERno1delete this library's content
updated_atINTEGER → BIGINTnonow()update time
  • Notes: a missing row means "allowed" — existing workspaces and newly invited members retain the historical shared-editing behavior until a library manager explicitly restricts them. Index idx_ws_kb_permissions_user(user_id).

workspace_invites — scoped invitations

12 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)wsi_rec + 12 hex chars
workspace_idTEXTnoFK→workspaces(id), CASCADE
tokenTEXTno (UNIQUE)invite token (192-bit genToken)
emailTEXTno''targeted email (empty = generic link)
roleTEXTno'guest'role granted on join: admin / member / guest
expires_atINTEGER → BIGINTno0expiry (0 = never)
max_usesINTEGERno1redemption capacity
used_countINTEGERno0uses so far
created_byTEXTyesNULLFK→users(id), SET NULL (account deletion does not destroy invites)
purposeTEXTno'manual'lifecycle metadata — manual (admin-created) / quick_link (bounded single-use link from the legacy rotate endpoint)
revoked_atINTEGER → BIGINTno0revocation time (0 = live)
created_atINTEGER → BIGINTnonow()creation time
  • Index: idx_ws_invites_workspace(workspace_id, created_at DESC).
  • Notes: the NOT NULL release on created_by is done during migration (PG via table rebuild, store/workspace_invites.go:487-562); max_uses/used_count are BIGINT in PG but INTEGER in SQLite (rebuild in workspace_invites.go:535).

workspace_policies — per-workspace capability policy

One row per workspace; can only narrow platform capabilities, never widen them. 16 columns.

ColumnTypeNullableDefaultNotes
workspace_idTEXTno (PK)FK→workspaces(id), CASCADE
allowed_model_idsTEXT (JSON)no'[]'allowed model ids; empty array = everything the platform offers
allowed_tool_idsTEXT (JSON)no'[]'allowed built-in tool ids (empty = all)
allowed_mcp_server_idsTEXT (JSON)no'[]'allowed admin MCP server ids (empty = all)
allow_sandboxINTEGERno1sandbox execution
allow_image_generationINTEGERno1image generation
allow_tool_callingINTEGERno1tool calling (backfilled on first split as the conjunction of the two old switches — never broadened, store.go:695-708)
allow_drawingINTEGERno1drawing (inherits the old allow_image_generation)
allow_mcpINTEGERno1use MCP
allow_skillsINTEGERno1use skills
allow_promptsINTEGERno1use prompts
allow_knowledge_basesINTEGERno1knowledge bases
allow_file_uploadINTEGERno1file upload
member_monthly_credit_limitREALno0per-member monthly credit cap (0 = unlimited; PG keeps REAL)
updated_byTEXTno''last-editing admin id
updated_atINTEGER → BIGINTno0last edit time (defaults to 0, not now() — the row may only be created on the first policy edit)

workspace_audit_logs — workspace audit

Lightweight audit trail. 8 columns.

ColumnTypeNullableDefaultNotes
idTEXTno (PK)audit row id
workspace_idTEXTnodeliberately no FK — rows outlive workspace deletion
actor_user_idTEXTnoactor (also without an FK, so account deletion preserves the audit trail)
actionTEXTnoaction name. Currently emitted: workspace.created / workspace.deleted / member.joined / member.left / member.removed / member.role_updated / member.permissions_updated / invite.created / invite.used / invite.revoked / invite.rotated / policy.updated / resource.visibility_changed (store/workspace_audit.go; free text, grows with features)
target_typeTEXTno''target kind written by the store audit layer (conversation / project / knowledge_base / workspace / workspace_member / workspace_invite; no CHECK constraint)
target_idTEXTno''target id
metadataTEXT (JSON)no'{}'context. Never contains invite tokens, API keys, request bodies, or document content
created_atINTEGER → BIGINTnonow()time
  • Index: idx_ws_audit_workspace(workspace_id, created_at DESC).

registration_domains — automatic enrollment rules

One row maps an exact email domain to one workspace. The platform administrator manages these records from the separate /admin/domains page. 4 columns.

ColumnTypeNullableDefaultNotes
domainTEXTno (PK)normalized domain after the last @; case-insensitive matching, no wildcard or subdomain expansion
workspace_idTEXTnoFK→workspaces(id), ON DELETE RESTRICT; a workspace cannot be deleted while a rule points to it
lock_personalINTEGERno00/1; when enabled, bound non-platform-admin users cannot access or create a personal space
enabledINTEGERno10/1; disabling stops future auto-enrollment but does not release existing bindings
  • Matching: example.com matches user@example.com only. It does not match user@sub.example.com; domains must be entered without a scheme, path, wildcard, or trailing dot.
  • Registration transaction: a new password or trusted OAuth account is created, bound in domain_users, and added as a workspace member in one transaction. A matching password registration requires email verification even when the global verification switch is off. Existing users are not retroactively enrolled when a rule is created.
  • Lifecycle: domain and workspace_id are immutable after creation. Delete and recreate a rule to change either value. Deleting a rule releases its user bindings and personal-space locks while preserving existing workspace membership and data.

domain_users — account bindings and overrides

One row records the domain rule that enrolled a user. 3 columns.

ColumnTypeNullableDefaultNotes
user_idTEXTno (PK)FK→users(id), ON DELETE CASCADE
domainTEXTnoFK→registration_domains(domain), ON DELETE CASCADE
lock_overrideINTEGERyesNULL0/1 explicit per-user exception; NULL inherits registration_domains.lock_personal
  • Binding survives email changes: the row is keyed by user_id, so editing a user's email does not silently remove enrollment or access restrictions.
  • Effective lock: COALESCE(lock_override, registration_domains.lock_personal). An unlocked override restores the personal-space switch for that account; the user still remains a member of the assigned workspace.
  • Platform administrators: accounts with the platform admin role are never locked by domain rules and can use a personal space. The rule applies to ordinary users created after it is enabled; an administrator can later change an individual user's override.