TenantAtlas/specs/068-workspaces-v2/data-model.md
2026-02-01 12:19:57 +01:00

82 lines
1.8 KiB
Markdown

# Data Model: Workspace Model, Memberships & Managed Tenants (v2)
**Date**: 2026-01-31
**Feature**: [spec.md](spec.md)
## Entities
### Workspace
Represents an organization/customer scope.
**Fields**
- `id` (bigint, PK)
- `name` (string, required)
- `slug` (string, nullable, unique)
- `status` (enum/string; values: `active`, `archived`; default `active`)
- `created_at`, `updated_at`
**Relationships**
- hasMany `WorkspaceMembership`
- hasMany `ManagedTenant`
**Validation / invariants**
- `name` required and non-empty
- `slug` unique when present
- archived workspaces cannot be selected as current workspace
---
### WorkspaceMembership
Associates a user to a workspace with one role.
**Fields**
- `id` (bigint, PK)
- `workspace_id` (FK workspaces)
- `user_id` (FK users)
- `role` (enum/string; values: `owner`, `manager`, `operator`, `readonly`)
- `created_at`, `updated_at`
**Constraints**
- Unique(`workspace_id`, `user_id`)
**Invariants**
- Last-owner guard: there must always be at least one `owner` membership per workspace.
---
### ManagedTenant
A Microsoft Entra/Intune tenant managed inside exactly one Workspace.
**Fields (v2 additions)**
- `workspace_id` (FK workspaces)
- `entra_tenant_id` (string)
**Constraints**
- Unique(`entra_tenant_id`) globally
- Index(`workspace_id`)
---
### User
Existing identity row.
**Fields (v2 additions)**
- `last_workspace_id` (nullable FK workspaces)
**Relationships**
- hasMany `WorkspaceMembership`
## State transitions
- Workspace `status`: `active``archived` (archived should not be selectable as current workspace)
- Membership role changes: `owner|manager|operator|readonly` with server-side last-owner guard
## Notes
- URL identity uses `slug` when present, else `id`.
- Current workspace context is session-backed with a DB fallback via `users.last_workspace_id`.