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

15 KiB
Raw Blame History

Feature Specification: Workspace Model, Memberships & Managed Tenants (v2)

Feature Branch: 068-workspaces-v2
Created: 2026-01-31
Status: Draft
Input: User description: "Spec 068 v2 — Workspace Model, Memberships & Managed Tenants (admin panel scope; replace tenant-scoped UI with workspace-scoped hierarchy; add workspace memberships/roles, switcher, 404/403 semantics, audit logs, and default workspace migration)"

Clarifications

Session 2026-01-31

  • Q: What should identify the workspace in URLs (id vs slug)? → A: Prefer slug in URLs; if missing, fall back to numeric id.
  • Q: How should current workspace selection be persisted? → A: Persist in session and also store a nullable last_workspace_id on the user.
  • Q: Should an Entra tenant id be globally unique or unique per workspace? → A: Global unique entra_tenant_id (a Managed Tenant belongs to exactly one Workspace).
  • Q: What should users with 0 workspace memberships see? → A: A neutral /admin/no-access page (not 404).

Routing, Scoping & Selection Semantics

Routing “planes”

This feature introduces Workspace-scoped admin routing and treats Workspace membership as a primary isolation boundary.

  • Workspace plane (workspace-scoped): /admin/w/{workspace}/...
  • Entry points (unscoped): /admin/choose-workspace, /admin/no-access

If the repository also has existing tenant-scoped routes (e.g. /admin/t/{tenant}/...), they MUST be made workspace-safe by ensuring the tenant belongs to the currently selected workspace (or by denying-as-not-found).

Workspace selection algorithm (deterministic)

When a signed-in user enters the admin panel:

  1. If the session has a selected workspace and it is still valid (user is a member, workspace not archived) → use it.
  2. Else if users.last_workspace_id is set and still valid → select it, store into session, and use it.
  3. Else if the user has exactly 1 active membership → auto-select that workspace, store into session + last_workspace_id, and use it.
  4. Else if the user has 2+ active memberships → route to /admin/choose-workspace.
  5. Else (0 memberships) → route to /admin/no-access.

If a selected workspace becomes invalid (membership removed or workspace archived), selection MUST be cleared and the algorithm re-run.

User Scenarios & Testing (mandatory)

User Story 1 - Choose workspace and get correct scope (Priority: P1)

As a signed-in user, I want the admin panel to require an active Workspace context so that everything I see and search is scoped to my current Workspace and cannot leak information about other Workspaces.

Why this priority: This is the foundation for correct navigation, isolation, and predictable operations in enterprise / MSP usage.

Independent Test: Can be tested by creating 2 workspaces, adding a user to only one, and verifying workspace-scoped navigation + 404 semantics for non-members.

Acceptance Scenarios:

  1. Given a user who is a member of exactly one Workspace, When they enter the admin panel entry point, Then they are automatically routed into that Workspace context.
  2. Given a user who is a member of multiple Workspaces, When they enter the admin panel entry point, Then they are routed to a "choose workspace" page and can select one.
  3. Given a user who is not a member of Workspace A, When they attempt to access any Workspace A scoped URL, Then they receive a not-found outcome (deny-as-not-found; no hints).
  4. Given a user is a member of Workspace A, When they switch to Workspace B, Then navigation and global search results reflect only data in Workspace B.

User Story 2 - Manage workspace members and roles (Priority: P2)

As a Workspace Owner or Manager, I want to add and manage Workspace members and their roles so that the right people can access the Workspace with the right level of permissions.

Why this priority: Multi-user operation is required for MSP/enterprise teams; role-based access is a core control.

Independent Test: Can be tested by creating a workspace with 2+ users and validating add/change/remove behavior, including last-owner protections and audit entries.

Acceptance Scenarios:

  1. Given a Workspace with an Owner, When the Owner adds an existing user as a member, Then the user can access the Workspace within the admin panel.
  2. Given a Workspace member, When an Owner changes that members role, Then their effective permissions change accordingly.
  3. Given a Workspace with exactly one Owner, When that Owner is removed or demoted, Then the system prevents the action and records that it was blocked.
  4. Given a user who is a member but lacks a required capability for an action, When they try to execute the action, Then the action is blocked with a forbidden outcome (member → 403 semantics) and does not mutate data.

User Story 3 - Onboard a managed tenant inside a workspace (Priority: P3)

As a Workspace Owner or Manager, I want to add a Managed Tenant into the current Workspace from a single canonical onboarding entry so that onboarding is consistent now and can later evolve into a wizard without changing the entry point.

Why this priority: Managed Tenants are the core “things being managed” and must clearly live under a Workspace.

Independent Test: Can be tested by adding a Managed Tenant under Workspace A and verifying it does not appear under Workspace B.

Acceptance Scenarios:

  1. Given an active Workspace context, When an authorized member starts "add managed tenant" onboarding, Then the resulting Managed Tenant belongs to the current Workspace.
  2. Given a Managed Tenant in Workspace A, When viewing managed tenants in Workspace B, Then the tenant is not visible.
  3. Given legacy onboarding entry points, When a user visits them, Then they are redirected into the canonical onboarding entry for the appropriate Workspace context.

[Add more user stories as needed, each with an assigned priority]

Edge Cases

  • User has zero Workspace memberships (newly provisioned account) → should see a neutral "no access" page without leaking other workspaces.
  • Previously-selected Workspace is archived or the users membership was removed → current selection must be invalidated and user returned to choose/no-access safely.
  • Two concurrent admins attempt to remove/demote the last owner at the same time → system still guarantees at least one owner.
  • Managed Tenant identifier is attempted to be added twice (duplicate Entra tenant id) → the second attempt is blocked without creating an ambiguous partial record.
  • Global search term matches records in a Workspace the user is not a member of → results must not hint those records exist.

Requirements (mandatory)

Constitution alignment (required): This feature changes access control and tenant-plane data ownership. The implementation MUST include:

  • tenant/workspace isolation guarantees,
  • safety gates for any write/change behavior (preview/confirmation/audit as applicable),
  • run visibility/traceability for any long-running work,
  • and automated tests that prove isolation and authorization.

If any security-relevant change intentionally skips run tracking, the implementation MUST still emit audit entries that allow incident review.

Constitution alignment (RBAC-UX): This feature introduces Workspace-scoped authorization for the admin panel. The implementation MUST:

  • explicitly enforce deny-as-not-found for non-members (no hints),
  • enforce forbidden outcomes for members who lack the required capability,
  • ensure global search is scoped to the active Workspace and non-member-safe,
  • use a centralized capability registry and deterministic role mapping (no ad-hoc string checks),
  • require explicit confirmation for destructive-like actions,
  • and include at least one positive and one negative authorization test.

Constitution alignment (OPS-EX-AUTH-001): Authentication handshakes may perform synchronous outbound requests during login, but this exception MUST NOT be used for monitoring/operations functionality.

Constitution alignment (BADGE-001): If this feature changes status-like badges (status/outcome/severity/risk/availability/boolean), the spec MUST describe how badge semantics stay centralized (no ad-hoc mappings) and which tests cover any new/changed values.

Functional Requirements

  • FR-001 (Workspace entity): System MUST support a Workspace concept as a top-level container representing an organization/customer context.
  • FR-002 (Workspace lifecycle): Authorized users MUST be able to create a Workspace and view Workspace details. Workspaces MUST be markable as active or archived.
  • FR-003 (Membership source of truth): System MUST support Workspace membership records that assign a role per (workspace, user). Membership MUST be the sole source of truth for Workspace access.
  • FR-004 (Membership constraints): System MUST prevent duplicate memberships for the same (workspace, user) pair.
  • FR-005 (Last owner protection): System MUST prevent removing or demoting the last remaining Owner of a Workspace.
  • FR-006 (Membership management): Workspace Owners/Managers MUST be able to add existing users as members, change member roles, and remove members.
  • FR-007 (Workspace switcher + persistence): System MUST provide a workspace switcher that lists only workspaces where the user is a member, and MUST persist the users current workspace selection for the session.
  • FR-008 (Choose-workspace routing): If a user is a member of multiple workspaces, system MUST provide a dedicated choose-workspace page and MUST require an explicit selection before accessing workspace-scoped pages.
  • FR-008a (No-access routing): If a user has zero workspace memberships, system MUST route them to a neutral "no access" page and MUST NOT return deny-as-not-found for this entry-point case.
  • FR-009 (Workspace-scoped URLs): System MUST require Workspace context for all Workspace-plane admin resources under /admin/w/{workspace}/..., except authentication and the choose-workspace/no-access entry points.
    • If tenant-scoped routes exist elsewhere (e.g. /admin/t/{tenant}/...), they MUST be workspace-safe by ensuring the tenant belongs to the currently selected workspace (or by denying-as-not-found).
  • FR-010 (Managed tenant belongs to workspace): Each Managed Tenant MUST belong to exactly one Workspace.
  • FR-011 (Managed tenant uniqueness): A Managed Tenant MUST be uniquely identifiable by its Entra tenant identifier, and the system MUST prevent duplicates.
  • FR-012 (Canonical onboarding entry): System MUST provide a single canonical "add managed tenant" entry inside Workspace context; legacy entry points MUST redirect to it without allowing creation in an incorrect scope.
  • FR-013 (Global search safety): Global search MUST return results only from the users current Workspace, and MUST not leak existence of records in other Workspaces.
  • FR-014 (404 vs 403 semantics): For any Workspace-scoped resource:
    • Non-member access MUST be deny-as-not-found.
    • Member access without the required capability MUST be forbidden.
  • FR-015 (Capabilities and role mapping): System MUST derive effective capabilities from Workspace role deterministically using a centralized role → capability mapping.
  • FR-016 (UI behavior for missing capability): For members without required capability, the UI MUST show relevant actions as disabled (where applicable) rather than hidden, and execution MUST still be blocked server-side.
  • FR-017 (Audit logging for membership changes): System MUST record audit events for membership add, role change, removal, and last-owner-blocked outcomes.

Clarified Authorization Rules

  • Workspace creation: any signed-in user MAY create a Workspace; the creator becomes an Owner.
  • Workspace membership management: only Workspace Owners/Managers.

Assumptions

  • Creating a Workspace is allowed for signed-in users in v2; the creator becomes an Owner of the new Workspace.
  • Workspace URL identifier prefers human-friendly slug, falling back to numeric id if a workspace does not have a slug.
  • Current workspace selection is persisted in session and also saved as a nullable last_workspace_id on the user.

Legacy Route Scope (explicit)

For v2, the only guaranteed legacy onboarding entry point that MUST redirect is:

  • /admin/new

Additional legacy routes may exist in the codebase; they should be enumerated and handled as part of implementation if discovered.

Out of Scope (v2)

  • Onboarding wizard UI (stepper-based experience)
  • Automated mapping from external groups to Workspace membership
  • Billing and multi-region concerns

Key Entities (include if feature involves data)

  • Workspace: Represents an organization/customer scope; includes display name, optional human-friendly key, and an active/archived status.
  • Workspace Membership: Associates a user to a workspace with one role (Owner/Manager/Operator/Readonly).
  • Managed Tenant: Represents a Microsoft/Entra/Intune tenant managed inside a Workspace; belongs to exactly one Workspace and is uniquely identified by Entra tenant id.
  • Workspace Context: The current active Workspace selection that scopes navigation and search.
  • Capability: A named permission (e.g., workspace management, member management, managed tenant management) derived from Workspace role via centralized mapping.
  • Audit Event: An immutable record of sensitive Workspace membership changes with minimal, non-secret details.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001 (Isolation): 100% of attempts by non-members to access a Workspace-scoped page result in a not-found outcome.
  • SC-002 (Correct scoping): 0 global search results are returned from outside the users current Workspace during acceptance testing.
  • SC-003 (Membership management): An Owner can add a member, change role, and remove member in under 2 minutes end-to-end using the admin UI.
  • SC-004 (Last-owner safety): 100% of attempts to remove/demote the last Owner are blocked and recorded.
  • SC-005 (Migration safety): After migration, 100% of existing Managed Tenants are associated with exactly one Workspace.