- PRD: docs/PRD-MS21.md (43 scope items across 6 phases) - Mission init via mosaic coord - TASKS.md: 31 tasks across 6 milestones - Orchestrator: Jarvis (OpenClaw hybrid orchestration) Refs: MS21-PLAN-001
14 KiB
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
- Build admin UI for user management (list, invite, deactivate, assign roles)
- Build workspace management UI (create, configure, manage members)
- Build team management UI (create teams within workspaces, assign members)
- Implement user invitation flow (email or link-based)
- Implement break-glass local authentication (bypass OIDC for emergency access)
- Enforce RBAC across all UI surfaces (show/hide based on role)
- Build admin Settings pages for workspace and platform configuration
- Migrate jarvis-brain data (95 tasks, 106 projects) into Mosaic Stack PostgreSQL
- 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)
- GET /api/admin/users — List all users with workspace memberships and roles
- POST /api/admin/users/invite — Generate invitation (email or link)
- PATCH /api/admin/users/:id — Update user metadata (deactivate, change global role)
- DELETE /api/admin/users/:id — Deactivate user (soft delete, preserve data)
- POST /api/admin/workspaces — Create workspace with owner assignment
- PATCH /api/admin/workspaces/:id — Update workspace settings
- POST /api/workspaces/:id/members — Add member to workspace with role
- PATCH /api/workspaces/:id/members/:userId — Change member role
- DELETE /api/workspaces/:id/members/:userId — Remove member from workspace
- POST /api/workspaces/:id/teams — Create team within workspace
- POST /api/workspaces/:id/teams/:teamId/members — Add member to team
- DELETE /api/workspaces/:id/teams/:teamId/members/:userId — Remove from team
- POST /api/import/tasks — Bulk import tasks from jarvis-brain format
- POST /api/import/projects — Bulk import projects from jarvis-brain format
S2: Break-Glass Authentication
- Add isLocalAuth boolean field to User model (Prisma migration)
- Add passwordHash optional field to User model (for local auth users)
- Implement /api/auth/local/login endpoint (email + password, bcrypt)
- Implement /api/auth/local/setup endpoint (first-time break-glass user creation, requires admin token from env)
- Break-glass user bypasses OIDC entirely — session-based auth via BetterAuth
- Environment variable BREAKGLASS_SETUP_TOKEN controls initial setup access
S3: Admin UI Pages (Frontend)
- /settings/users — User management page (table: name, email, role, status, workspaces, last login)
- User detail/edit dialog (change role, deactivate, view workspace memberships)
- Invite user dialog (email input, workspace selection, role selection)
- /settings/workspaces — Workspace management page (list workspaces, member counts)
- Workspace detail page (members list, team list, settings)
- Add/remove workspace member dialog with role picker
- /settings/teams — Team management within workspace context
- Create team dialog, add/remove team members
S4: RBAC UI Enforcement
- Navigation sidebar: show/hide admin items based on user role
- Settings pages: restrict access to admin-only pages
- Action buttons: disable/hide create/delete based on permission level
- User profile: show current role and workspace memberships
S5: Data Migration
- Build scripts/migrate-brain.ts — TypeScript migration script
- Read jarvis-brain data/tasks/*.json (v2.0 format: { version, domain, tasks: [...] })
- Read jarvis-brain data/projects/*.json (format: { version, project: {...}, tasks: [...] })
- 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)
- Map brain priority to Mosaic TaskPriority (critical/high->HIGH, medium->MEDIUM, low->LOW)
- Create Domain records for unique domains (work, homelab, finances, family, etc.)
- Create Project records from project data, linking to domains
- Create Task records linking to projects, preserving brain-specific fields in metadata JSON
- Preserve blocks, blocked_by, repo, branch, current_milestone, notes in task/project metadata
- Dry-run mode with validation report before actual import
- 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
- Jason (admin) can invite Melanie to a shared workspace
- Jason can create a "USC IT" workspace and invite employees
- Each user sees only their workspace(s) data
- Break-glass user can log in without Authentik being available
- Admin pages are only visible to OWNER/ADMIN roles
- Data from jarvis-brain appears in the dashboard, kanban, and project pages after migration
- 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
- Baseline: pnpm lint && pnpm build && pnpm test must pass
- Unit tests for AdminService (user CRUD, invitation flow, permission checks)
- Unit tests for LocalAuthService (bcrypt hashing, token validation)
- Unit tests for AdminController (route guards, DTO validation)
- Integration test: invitation -> acceptance -> workspace access
- Integration test: break-glass setup -> login -> workspace access
- Integration test: RBAC prevents unauthorized role changes
- Migration script test: dry-run produces valid report, apply creates correct records
- Frontend: admin pages render with correct role gating
- 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
- Risk: BetterAuth credential provider may require specific adapter configuration. Mitigation: Review BetterAuth docs for credential auth alongside OIDC; test in isolation first.
- Risk: Schema migration on production database. Mitigation: Use Prisma migration with --create-only for review before applying.
- 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.
- Open: Should deactivated users retain active sessions? ASSUMPTION: No, deactivation immediately invalidates all sessions. Rationale: Security best practice.
- 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
- User deactivation is soft-delete via deactivatedAt field. Hard delete cascades and destroys workspace data.
- BetterAuth supports custom credential providers alongside OIDC.
- Invitation flow uses link-based tokens (no email service required initially).
- Break-glass auth is off by default, controlled by ENABLE_LOCAL_AUTH env var.
- Migration script runs outside the API server as a standalone script.
- Admin pages follow existing Settings page UI patterns (cards, tables, dialogs).