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

106 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Research: Workspace Model, Memberships & Managed Tenants (v2)
**Date**: 2026-01-31
**Feature**: [spec.md](spec.md)
This document consolidates the key design decisions needed to implement the spec in this repo.
## Decisions
### 1) Workspace identifier in URLs
**Decision**: Prefer Workspace `slug` in URLs; fall back to numeric `id` when a workspace has no slug.
**Rationale**:
- Human-friendly URLs for admins (`/admin/w/acme/...`).
- Keeps migration pain low because slugs can be introduced incrementally.
**Alternatives considered**:
- Always numeric id: easiest but less readable.
- Slug-only required: clean but increases migration/validation burden.
---
### 2) Persisting current workspace selection
**Decision**: Persist `current_workspace_id` in session AND store a nullable `users.last_workspace_id`.
**Rationale**:
- Session is canonical for the current request.
- DB persistence improves UX across new sessions / devices without requiring the user to re-select every time.
**Alternatives considered**:
- Session only: simpler but annoying across logins.
- URL-only: makes deep links harder and doesnt support “default workspace” semantics.
---
### 3) Managed Tenant uniqueness
**Decision**: Entra tenant id is globally unique (a Managed Tenant belongs to exactly one Workspace).
**Rationale**:
- Avoids ambiguous ownership and accidental double-management of the same Microsoft tenant.
- Aligns with “no tenant-in-tenant” goal.
**Alternatives considered**:
- Unique per workspace: enables duplicates but creates confusing operational ownership.
---
### 4) Zero-membership entry behavior
**Decision**: Show a neutral `/admin/no-access` page for users with 0 memberships (not 404).
**Rationale**:
- Clear UX while still not leaking any workspace existence.
**Alternatives considered**:
- 404: secure but confusing; users think the app is broken.
---
### 5) How to scope the admin panel without Filament tenancy
**Decision**: Make Workspace context a first-class routing concern (`/admin/w/{workspace}/...`) enforced via middleware + session context. Do not use Filament tenancy (`/admin/t/{tenant}`) as the primary structure.
**Rationale**:
- Meets “no tenant-in-tenant” and removes the overloaded “tenant” concept in UI.
- Middleware is the cleanest place to enforce deny-as-not-found for non-members.
**Alternatives considered**:
- Use Filament tenancy to represent workspaces: would keep the same tenancy mechanisms but continues the “tenant-in-tenant” confusion.
---
### 6) Global search scoping
**Decision**: Global search must be scoped to the active Workspace and return no results outside it.
**Rationale**:
- Prevents leakage (no hints) and aligns with constitution RBAC-UX.
- Repo already has a tenant-scoped global search trait; we can introduce a workspace-scoped variant.
**Alternatives considered**:
- Hide search results at render time: insufficient, because global search queries must also be scoped.
---
### 7) Audit logging for workspace membership changes
**Decision**: Introduce a workspace-level audit log stream for membership mutations.
**Rationale**:
- Existing `audit_logs` table is tenant-scoped (requires `tenant_id`) and cannot represent workspace-only changes cleanly.
- Additive schema avoids breaking existing auditing.
**Alternatives considered**:
- Make `audit_logs.tenant_id` nullable and add `workspace_id`: higher migration and code risk.
## Open Questions (implementation-level)
These are not spec ambiguities but implementation choices to resolve while coding:
- Whether to build a generic “scope context” abstraction (tenant/workspace) or implement a workspace-specific parallel to the existing tenant helpers.
- How to progressively migrate existing Filament resources from `/admin/t/{tenant}` to `/admin/w/{workspace}` without breaking deep links (redirect strategy).