Last updated 2026-06-29

Workspaces & Groups

This page explains the two membership primitives this service exposes and why they are deliberately separate. A workspace is a tenancy / ownership boundary that people belong to at a role; a group is a reusable, nestable set of subjects you grant access with. WorkspaceService manages the workspace namespace; GroupService manages the group namespace (see Authorization Model).

Every management call carries acting_user_id (the user the action is authorized as — required; omitting it returns InvalidArgument) and an optional project_id (empty = the default project). The caller is a trusted product backend, not the end user — see Security & Service Auth.

Personal vs. team workspaces

The product models the full B2C↔B2B spectrum with two workspace kinds, the way Claude and ChatGPT do (see ADR-0002).

PERSONALTEAM
How it appearsAuto-provisioned by ListWorkspaces on first callCreated by CreateWorkspace
MembershipExactly one member — the ownerMany members, each at a role
Add membersNo — AddMember / CreateInvitation rejectedYes
DeletableNo — DeleteWorkspace rejectedYes, by its owner

Every user owns exactly one PERSONAL workspace, created lazily the first time they call ListWorkspaces, so a user never has a "no workspace" state. A personal workspace is closed: single-member and undeletable, enforced structurally by the WorkspaceType enum rather than by counting members. Team workspaces are user-created for collaboration — a family or a whole company.

curl -X POST http://localhost:8080/workspace.v1.WorkspaceService/ListWorkspaces \
-H "Authorization: Bearer $WS_SERVICE_TOKEN" -H "Content-Type: application/json" \
-d '{"acting_user_id":"alice"}'
# On alice's first call this auto-provisions and returns her PERSONAL workspace.

Roles

Every team membership carries a Role, ordered owner ⊃ admin ⊃ member ⊃ guest. Each grade includes the powers of every grade below it.

RoleCan do
ROLE_OWNEREverything an admin can, plus delete the workspace and transfer ownership. Set at creation; never granted afterward.
ROLE_ADMINAdd / remove members, change member roles (not owner), create / revoke invitations, update the workspace.
ROLE_MEMBERBelong to the workspace; gains viewer on resources in it through inheritance.
ROLE_GUESTThe weakest grade — present in the workspace for narrowly scoped, explicitly shared access.

Gotcha — owner is not grantable. AddMember, UpdateMemberRole, and CreateInvitation reject ROLE_OWNER with InvalidArgument. Ownership is set when the workspace is created and transferred explicitly — there is never a second owner. The owner's role cannot be changed and the owner cannot be removed.

Membership maps to tuples

Each membership is mirrored as a workspace:<id>#<role>@user:<id> tuple, so a Check against the workspace namespace honours it immediately and resource inheritance treats personal and team workspaces identically.

workspace:acme#owner@user:ceo
workspace:acme#admin@user:eng-lead
workspace:acme#member@user:alice

Adding eng-lead as an admin writes the second tuple:

curl -X POST http://localhost:8080/workspace.v1.WorkspaceService/AddMember \
-H "Authorization: Bearer $WS_SERVICE_TOKEN" -H "Content-Type: application/json" \
-d '{"workspace_id":"acme","user_id":"eng-lead","role":"ROLE_ADMIN","acting_user_id":"ceo"}'

Invitations

Invitations bring a person into a team workspace by email when you do not yet know their user id.

  • CreateInvitation(workspace_id, email, role) returns a one-time plaintext token, echoed only on creation, for out-of-band delivery (your product emails it). Like AddMember, it rejects ROLE_OWNER.
  • The token has a 7-day TTL. After it expires the invite moves to EXPIRED and can no longer be accepted.
  • AcceptInvitation(token, acting_user_id) consumes the token — it is single-use — and adds acting_user_id to the workspace at the invited role. If they are already a member, the invite is consumed and the existing role is kept.
  • ListInvitations returns pending invites but never echoes tokens. RevokeInvitation cancels a pending one.
curl -X POST http://localhost:8080/workspace.v1.WorkspaceService/CreateInvitation \
-H "Authorization: Bearer $WS_SERVICE_TOKEN" -H "Content-Type: application/json" \
-d '{"workspace_id":"acme","email":"new@example.com","role":"ROLE_MEMBER","acting_user_id":"ceo"}'
# => {"invitation":{ ... ,"token":"<one-time-token>"}} ← only place the token appears

Groups — a separate, nestable primitive

A group is a reusable set of subjects — an email distribution list, a chat group, an on-call rotation, a B2C user's "family" list. It is deliberately not a workspace (see ADR-0003). Groups map to the group namespace, relation member, whose rewrite is this — just stored tuples.

Because a tuple's subject can be a userset, granting a group access to anything is a single tuple, and groups nest (a GroupMember is a oneof of a user_id or a nested group_id):

group:all-eng#member@group:backend#member # all-eng contains backend (nesting)
workspace:acme#member@group:all-eng#member # all engineers are acme members
resource:incident-7#editor@group:sre#member # on-call can edit the incident
resource:task-buy-milk#viewer@group:family#member # a B2C user's reusable "family" list

A group is reusable: the same group is referenced by id from any number of workspace or resource tuples. It may be project-level (empty workspace_id — a B2C user's standalone "family" list) or scoped to a workspace (a company's internal groups). Scoping is an organisational convenience for listing and ownership, not a reuse boundary — a workspace-scoped group can still be granted access on any resource in the same project.

Workspace or group?

WorkspaceGroup
What it isA tenancy / ownership boundary people belong toA named set of subjects you grant access with
OwnerExactly one ownerNo role hierarchy — flat membership
Rolesowner ⊃ admin ⊃ member ⊃ guestJust member
Nests?NoYes — groups contain groups
ReuseOwns its resources; not referenced elsewhereReferenced from many workspaces & resources
Resource inheritanceResources inherit from their parent workspaceNone — a group is only ever a subject
Use it whenIt owns resources and is a billing / tenancy unitIt is a set of people you reuse across grants

Rule of thumb: if it owns resources and bills, it is a workspace; if it is a named set of people you grant access with, it is a group.

Where to next