# PRD: MS21 — Multi-Tenant Platform, RBAC Enforcement, and Data Migration

## Metadata

- Owner: Jason Woltje
- Orchestrator: Jarvis (OpenClaw)
- Date: 2026-02-28
- Status: in-progress
- Version: 0.0.21

## Problem Statement

Mosaic Stack is a single-user deployment. The Workspace, Team, WorkspaceMember, and RBAC infrastructure exist in the schema and codebase (PermissionGuard, WorkspaceGuard, roles: OWNER/ADMIN/MEMBER/GUEST), but there is no admin UI for managing users, workspaces, or teams. There is no invitation flow, no user management page, and no break-glass authentication mechanism for emergency access without OIDC. Additionally, the platform has no real operational data — jarvis-brain contains 106 projects and 95 tasks that need migrating into Mosaic Stack to make the dashboard, kanban, and project pages useful.

## Objectives

1. Build admin UI for user management (list, invite, deactivate, assign roles)
2. Build workspace management UI (create, configure, manage members)
3. Build team management UI (create teams within workspaces, assign members)
4. Implement user invitation flow (email or link-based)
5. Implement break-glass local authentication (bypass OIDC for emergency access)
6. Enforce RBAC across all UI surfaces (show/hide based on role)
7. Build admin Settings pages for workspace and platform configuration
8. Migrate jarvis-brain data (95 tasks, 106 projects) into Mosaic Stack PostgreSQL
9. Build data import API endpoint for bulk task/project creation

## Completed Work

### Existing Infrastructure (Already Built)

| Component                                                         | Status   | Location                                                |
| ----------------------------------------------------------------- | -------- | ------------------------------------------------------- |
| Prisma models: User, Workspace, WorkspaceMember, Team, TeamMember | Complete | apps/api/prisma/schema.prisma                           |
| WorkspaceMemberRole enum (OWNER, ADMIN, MEMBER, GUEST)            | Complete | schema.prisma                                           |
| TeamMemberRole enum (OWNER, ADMIN, MEMBER)                        | Complete | schema.prisma                                           |
| PermissionGuard with role hierarchy                               | Complete | apps/api/src/common/guards/permission.guard.ts          |
| Permission decorator (@RequirePermission)                         | Complete | apps/api/src/common/decorators/permissions.decorator.ts |
| Permission enum (WORKSPACE_OWNER, ADMIN, MEMBER, ANY)             | Complete | permissions.decorator.ts                                |
| WorkspaceGuard                                                    | Complete | apps/api/src/common/guards/workspace.guard.ts           |
| RBAC applied to 18+ controllers                                   | Complete | All resource controllers                                |
| Workspaces CRUD API                                               | Complete | apps/api/src/workspaces/                                |
| BetterAuth + Authentik OIDC                                       | Complete | apps/api/src/auth/                                      |
| AdminGuard                                                        | Complete | apps/api/src/auth/guards/admin.guard.ts                 |

## Scope

### In Scope (MS21)

#### S1: Admin API Endpoints (Backend)

1. GET /api/admin/users — List all users with workspace memberships and roles
2. POST /api/admin/users/invite — Generate invitation (email or link)
3. PATCH /api/admin/users/:id — Update user metadata (deactivate, change global role)
4. DELETE /api/admin/users/:id — Deactivate user (soft delete, preserve data)
5. POST /api/admin/workspaces — Create workspace with owner assignment
6. PATCH /api/admin/workspaces/:id — Update workspace settings
7. POST /api/workspaces/:id/members — Add member to workspace with role
8. PATCH /api/workspaces/:id/members/:userId — Change member role
9. DELETE /api/workspaces/:id/members/:userId — Remove member from workspace
10. POST /api/workspaces/:id/teams — Create team within workspace
11. POST /api/workspaces/:id/teams/:teamId/members — Add member to team
12. DELETE /api/workspaces/:id/teams/:teamId/members/:userId — Remove from team
13. POST /api/import/tasks — Bulk import tasks from jarvis-brain format
14. POST /api/import/projects — Bulk import projects from jarvis-brain format

#### S2: Break-Glass Authentication

15. Add isLocalAuth boolean field to User model (Prisma migration)
16. Add passwordHash optional field to User model (for local auth users)
17. Implement /api/auth/local/login endpoint (email + password, bcrypt)
18. Implement /api/auth/local/setup endpoint (first-time break-glass user creation, requires admin token from env)
19. Break-glass user bypasses OIDC entirely — session-based auth via BetterAuth
20. Environment variable BREAKGLASS_SETUP_TOKEN controls initial setup access

#### S3: Admin UI Pages (Frontend)

21. /settings/users — User management page (table: name, email, role, status, workspaces, last login)
22. User detail/edit dialog (change role, deactivate, view workspace memberships)
23. Invite user dialog (email input, workspace selection, role selection)
24. /settings/workspaces — Workspace management page (list workspaces, member counts)
25. Workspace detail page (members list, team list, settings)
26. Add/remove workspace member dialog with role picker
27. /settings/teams — Team management within workspace context
28. Create team dialog, add/remove team members

#### S4: RBAC UI Enforcement

29. Navigation sidebar: show/hide admin items based on user role
30. Settings pages: restrict access to admin-only pages
31. Action buttons: disable/hide create/delete based on permission level
32. User profile: show current role and workspace memberships

#### S5: Data Migration

33. Build scripts/migrate-brain.ts — TypeScript migration script
34. Read jarvis-brain data/tasks/\*.json (v2.0 format: { version, domain, tasks: [...] })
35. Read jarvis-brain data/projects/\*.json (format: { version, project: {...}, tasks: [...] })
36. Map brain status to Mosaic TaskStatus (done->COMPLETED, in-progress->IN_PROGRESS, backlog/pending/scheduled/not-started/planned->NOT_STARTED, blocked/on-hold->PAUSED, cancelled->ARCHIVED)
37. Map brain priority to Mosaic TaskPriority (critical/high->HIGH, medium->MEDIUM, low->LOW)
38. Create Domain records for unique domains (work, homelab, finances, family, etc.)
39. Create Project records from project data, linking to domains
40. Create Task records linking to projects, preserving brain-specific fields in metadata JSON
41. Preserve blocks, blocked_by, repo, branch, current_milestone, notes in task/project metadata
42. Dry-run mode with validation report before actual import
43. Idempotent — skip records that already exist (match by metadata.brainId)

### Out of Scope

- Federation (MS22)
- Agent task mapping and telemetry (MS23)
- Playwright E2E tests (MS24)
- Email delivery service (invitations stored as links for now)
- User self-registration (admin-only invitation model)

## User/Stakeholder Requirements

1. Jason (admin) can invite Melanie to a shared workspace
2. Jason can create a "USC IT" workspace and invite employees
3. Each user sees only their workspace(s) data
4. Break-glass user can log in without Authentik being available
5. Admin pages are only visible to OWNER/ADMIN roles
6. Data from jarvis-brain appears in the dashboard, kanban, and project pages after migration
7. All existing tests continue to pass after changes

## Functional Requirements

### FR-001: Admin User Management API

- AdminGuard protects all /api/admin/\* routes
- List users returns: id, name, email, emailVerified, createdAt, workspace memberships with roles
- Invite creates a User record with a pending invitation token
- Deactivate sets a deactivatedAt timestamp (new field), does not delete data
- ASSUMPTION: User deactivation is soft-delete via new deactivatedAt field on User model. Rationale: Hard delete would cascade and destroy workspace data.

### FR-002: Workspace Member Management API

- Add member requires WORKSPACE_ADMIN or WORKSPACE_OWNER permission
- Role changes require equal or higher permission (MEMBER cannot promote to ADMIN)
- Cannot remove the last OWNER of a workspace
- Cannot change own role to lower than OWNER if sole owner

### FR-003: Break-Glass Authentication

- Local auth is opt-in per deployment via ENABLE_LOCAL_AUTH=true env var
- Break-glass setup requires a one-time setup token from environment
- Password stored as bcrypt hash, minimum 12 characters
- Break-glass user gets OWNER role on default workspace
- Session management identical to OIDC users (BetterAuth session tokens)
- ASSUMPTION: BetterAuth supports custom credential providers alongside OIDC. Rationale: BetterAuth documentation confirms credential-based auth as a built-in feature.

### FR-004: Admin UI

- User management table with search, sort, filter by role/status
- Inline role editing via dropdown
- Confirmation dialog for destructive actions (deactivate user, remove from workspace)
- PDA-friendly language: "Deactivate" not "Delete", "Pending" not "Expired"
- Responsive design matching existing Settings page patterns
- Dark/light theme support via existing design token system

### FR-005: Data Migration Script

- Standalone TypeScript script in scripts/migrate-brain.ts
- Reads from jarvis-brain path (configurable via --brain-path flag)
- Connects to Mosaic Stack database via DATABASE_URL
- Requires target workspace ID (--workspace-id flag)
- Requires creator user ID (--user-id flag)
- Outputs validation report before writing (dry-run by default)
- --apply flag to execute the migration
- Creates Activity log entries for all imported records

## Technical Design

### Schema Changes (Prisma Migration)

Add to User model:

- deactivatedAt DateTime? (soft delete)
- isLocalAuth Boolean default(false)
- passwordHash String? (bcrypt hash for local auth)
- invitedBy String? Uuid (FK to inviting user)
- invitationToken String? unique
- invitedAt DateTime?

### New NestJS Module: AdminModule

Location: apps/api/src/admin/
Files: admin.module.ts, admin.controller.ts, admin.service.ts, DTOs, specs

### New NestJS Module: LocalAuthModule (Break-Glass)

Location: apps/api/src/auth/local/
Files: local-auth.controller.ts, local-auth.service.ts, DTOs, specs

### Frontend Pages

Location: apps/web/app/(authenticated)/settings/

- users/page.tsx — User management
- workspaces/page.tsx — Workspace list
- workspaces/[id]/page.tsx — Workspace detail (members, teams)
- teams/page.tsx — Team management

## Testing and Verification

1. Baseline: pnpm lint && pnpm build && pnpm test must pass
2. Unit tests for AdminService (user CRUD, invitation flow, permission checks)
3. Unit tests for LocalAuthService (bcrypt hashing, token validation)
4. Unit tests for AdminController (route guards, DTO validation)
5. Integration test: invitation -> acceptance -> workspace access
6. Integration test: break-glass setup -> login -> workspace access
7. Integration test: RBAC prevents unauthorized role changes
8. Migration script test: dry-run produces valid report, apply creates correct records
9. Frontend: admin pages render with correct role gating
10. All 4,772+ existing tests continue to pass

## Quality Gates

pnpm lint && pnpm build && pnpm test

## Delivery Phases

| Phase                   | Focus                                                                   | Tasks             |
| ----------------------- | ----------------------------------------------------------------------- | ----------------- |
| P1: Schema + Admin API  | Prisma migration, AdminModule, user/workspace/team management endpoints | S1 items 1-12     |
| P2: Break-Glass Auth    | LocalAuthModule, credential provider, setup flow                        | S2 items 15-20    |
| P3: Data Migration      | Migration script, jarvis-brain to PostgreSQL                            | S5 items 33-43    |
| P4: Admin UI            | Settings pages for users, workspaces, teams                             | S3 items 21-28    |
| P5: RBAC UI Enforcement | Frontend permission gating, navigation filtering                        | S4 items 29-32    |
| P6: Verification        | Full test pass, deployment, smoke test                                  | All quality gates |

## Risks and Open Questions

1. Risk: BetterAuth credential provider may require specific adapter configuration. Mitigation: Review BetterAuth docs for credential auth alongside OIDC; test in isolation first.
2. Risk: Schema migration on production database. Mitigation: Use Prisma migration with --create-only for review before applying.
3. Risk: jarvis-brain data has fields that don't map 1:1 to Mosaic schema. Mitigation: Use metadata JSON field for overflow; validate with dry-run.
4. Open: Should deactivated users retain active sessions? ASSUMPTION: No, deactivation immediately invalidates all sessions. Rationale: Security best practice.
5. Open: Should break-glass user be auto-created on first deployment? ASSUMPTION: No, requires explicit setup via API with env token. Rationale: Prevents accidental exposure.

## Assumptions

1. User deactivation is soft-delete via deactivatedAt field. Hard delete cascades and destroys workspace data.
2. BetterAuth supports custom credential providers alongside OIDC.
3. Invitation flow uses link-based tokens (no email service required initially).
4. Break-glass auth is off by default, controlled by ENABLE_LOCAL_AUTH env var.
5. Migration script runs outside the API server as a standalone script.
6. Admin pages follow existing Settings page UI patterns (cards, tables, dialogs).
