# Feature Specification: Workspace-Managed Tenant Administration Migration

**Feature Branch**: `080-workspace-managed-tenant-admin`  
**Created**: 2026-02-07  
**Status**: Draft (implementation-ready)  
**Input**: User description: "Make Manage workspace-scoped (/admin) and Operate tenant-scoped (/admin/t/{tenant}). Eliminate management CRUD from /admin/t/*"

This feature migrates all tenant administration surfaces out of the Filament tenant scope (`/admin/t/{tenant}/*`) into workspace-scoped routes (`/admin/*`). Tenant scope is reserved strictly for operational modules.

**Separation rule (normative):**
- Workspace panel (`/admin/*`) = Manage (tenants, memberships, provider connections, required permissions, onboarding/activation, monitoring).
- Tenant panel (`/admin/t/{tenant}/*`) = Operate (inventory, drift, policies, backups, directory, etc.).

**Out of scope for this feature:** introducing a new tenant panel “Home” page at `/admin/t/{tenant}`.

## Clarifications

### Session 2026-02-07

- Q: Should management pages be viewable for workspace members without manage capabilities? → A: Yes. Management pages are viewable for workspace members; only mutations are capability-gated (403).
- Q: Should this feature include a dedicated tenant panel “Home” page at `/admin/t/{tenant}`? → A: No. Do not add a new tenant home page in this feature.
- Q: For workspace-managed routes like `/admin/tenants/{tenant}`, what should `{tenant}` be? → A: `Tenant.external_id` (Entra tenant GUID), same identifier used by Filament tenancy under `/admin/t/{tenant}`.
- Q: Should “Managed Tenants” appear in Filament Global Search? → A: Yes, in the workspace panel only; tenant panel must not expose tenant-management entities in search.
- Q: For workspace-managed tenant management routes, what is the canonical URL shape? → A: `/admin/tenants*` (workspace is selected in context/session; middleware enforces it), not `/admin/w/{workspace}/tenants*`.

## User Scenarios & Testing *(mandatory)*

<!--
  IMPORTANT: User stories should be PRIORITIZED as user journeys ordered by importance.
  Each user story/journey must be INDEPENDENTLY TESTABLE - meaning if you implement just ONE of them,
  you should still have a viable MVP (Minimum Viable Product) that delivers value.
  
  Assign priorities (P1, P2, P3, etc.) to each story, where P1 is the most critical.
  Think of each story as a standalone slice of functionality that can be:
  - Developed independently
  - Tested independently
  - Deployed independently
  - Demonstrated to users independently
-->

### User Story 1 - Manage tenants from workspace scope (Priority: P1)

As a workspace member, I can manage (view/create/configure) managed tenants under `/admin/tenants*` without needing to enter tenant scope.

**Why this priority**: It removes the “henne-ei” context issue and makes `/admin` the canonical management surface.

**Independent Test**: Fully testable via HTTP requests asserting 200/404 and basic page visibility under `/admin/tenants*`.

**Acceptance Scenarios**:

1. **Given** I am a workspace member, **When** I visit `/admin/tenants`, **Then** I can access the Tenants management list.
2. **Given** I am a workspace member, **When** I visit `/admin/tenants/{tenant}`, **Then** I can access the Tenant management overview.
3. **Given** I am not a workspace member, **When** I visit `/admin/tenants` or `/admin/tenants/{tenant}`, **Then** I receive a 404 (deny-as-not-found).

---

### User Story 2 - Operate tenant modules only when entitled (Priority: P2)

As a user, I can operate inside a managed tenant under `/admin/t/{tenant}/*` only when I’m entitled to that tenant.

**Why this priority**: It preserves tenant isolation and makes 404/403 semantics predictable.

**Independent Test**: Fully testable via an operational page route asserting 200 for entitled users and 404 for non-entitled users.

**Acceptance Scenarios**:

1. **Given** I am a workspace member and entitled to the selected tenant, **When** I visit an operational route under `/admin/t/{tenant}/*`, **Then** I can access it.
2. **Given** I am a workspace member but not entitled to the selected tenant, **When** I visit an operational route under `/admin/t/{tenant}/*`, **Then** I receive a 404 (deny-as-not-found).

---

### User Story 3 - Tenant-scoped management routes are removed (Priority: P3)

As a user, I cannot access tenant-scoped management CRUD routes anymore; they are removed and should not resolve.

**Why this priority**: It enforces IA separation via route registration (not just UI hiding).

**Independent Test**: Fully testable via HTTP 404 assertions for a set of removed routes.

**Acceptance Scenarios**:

1. **Given** any user, **When** I request `/admin/t/{tenant}/provider-connections`, **Then** I receive 404 because the route does not exist.
2. **Given** any user, **When** I request `/admin/t/{tenant}/required-permissions`, **Then** I receive 404 because the route does not exist.
3. **Given** any user, **When** I request a tenant-scoped tenant-management route (e.g. `/admin/t/{tenant}/tenants/*`), **Then** I receive 404 because the route does not exist.

---

### User Story 4 - Management actions enforce capability semantics (Priority: P2)

As a workspace member, I can see management pages, but mutations are forbidden unless I have the required capability.

**Why this priority**: It preserves enterprise RBAC semantics (404 for non-membership; 403 for missing capability on mutation).

**Independent Test**: Test a representative management mutation and assert 403 when capability is missing.

**Acceptance Scenarios**:

1. **Given** I am a workspace member without the relevant manage capability, **When** I attempt a management mutation (e.g., change role, set default connection), **Then** the server responds 403.

---

### User Story 5 - Global search isolation (Priority: P2)

As a user, global search does not leak tenant management entities across scopes.

**Why this priority**: It prevents discovery leaks and aligns with deny-as-not-found semantics.

**Independent Test**: Test that workspace global search can find allowed tenants; tenant panel search does not expose tenant-management entities; non-members discover nothing.

**Acceptance Scenarios**:

1. **Given** I am a workspace member, **When** I use workspace global search, **Then** I can find tenants I can access.
2. **Given** I am in tenant panel, **When** I use global search, **Then** it does not expose tenant-management entities.
3. **Given** I am not a workspace member, **When** I use global search, **Then** I do not discover tenant existence.

---

### Edge Cases

- Direct navigation to removed tenant-scoped management URLs.
- Stale internal CTAs/links that previously pointed at `/admin/t/{tenant}` management screens.
- Tenant slug/identifier mismatch (requesting a tenant not belonging to the active workspace context).
- Cross-scope leakage via global search results or navigation items.
- Non-member users attempting to infer tenant/workspace existence.

## Requirements *(mandatory)*

**Constitution alignment (required):** If this feature introduces any Microsoft Graph calls, any write/change behavior,
or any long-running/queued/scheduled work, the spec MUST describe contract registry updates, safety gates
(preview/confirmation/audit), tenant isolation, run observability (`OperationRun` type/identity/visibility), and tests.
If security-relevant DB-only actions intentionally skip `OperationRun`, the spec MUST describe `AuditLog` entries.

**Constitution alignment (RBAC-UX):** If this feature introduces or changes authorization behavior, the spec MUST:
- state which authorization plane(s) are involved (tenant `/admin/t/{tenant}` vs platform `/system`),
- ensure any cross-plane access is deny-as-not-found (404),
- explicitly define 404 vs 403 semantics:
  - non-member / not entitled to tenant scope → 404 (deny-as-not-found)
  - member but missing capability → 403
- describe how authorization is enforced server-side (Gates/Policies) for every mutation/operation-start/credential change,
- reference the canonical capability registry (no raw capability strings; no role-string checks in feature code),
- ensure global search is tenant-scoped and non-member-safe (no hints; inaccessible results treated as 404 semantics),
- ensure destructive-like actions require confirmation (`->requiresConfirmation()`),
- include at least one positive and one negative authorization test, and note any RBAC regression tests added/updated.

**Constitution alignment (OPS-EX-AUTH-001):** OIDC/SAML login handshakes may perform synchronous outbound HTTP (e.g., token exchange)
on `/auth/*` endpoints without an `OperationRun`. This MUST NOT be used for Monitoring/Operations pages.

**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

**Principles (normative):**
- “Tenants” means the workspace-managed tenants list (CRUD under `/admin/tenants*`).
- “Switch tenant” is a context action in tenant scope; it must not behave like a CRUD list.
- Management must not depend on being in tenant scope; tenant scope must not expose tenant administration CRUD.

**Routing / IA (normative):**
- Workspace management is canonical under `/admin/*`:
  - `/admin/tenants` (list/create)
  - `/admin/tenants/{tenant}` (manage overview)
  - `/admin/tenants/{tenant}/memberships`
  - `/admin/tenants/{tenant}/provider-connections`
  - `/admin/tenants/{tenant}/required-permissions`
  - `/admin/tenants/{tenant}/onboarding` (optional entry)
  - `/admin/operations` and `/admin/operations/{run}` (monitoring)
- Tenant scope is operate-only under `/admin/t/{tenant}/*` (inventory, drift, policies, backups/restore, directory, etc.).

**Workspace context:**
- Workspace-scoped management routes MUST use the `/admin/tenants*` shape and rely on the selected workspace context (e.g., middleware) rather than including `{workspace}` in every management URL.

**Route parameter identity (normative):**
- `{tenant}` in both workspace-managed routes (`/admin/tenants/{tenant}/*`) and tenant-scoped routes (`/admin/t/{tenant}/*`) MUST refer to the managed tenant identifier `Tenant.external_id` (Entra tenant GUID).

**Authorization semantics (mandatory):**
- Non-member / not entitled to scope: 404 (deny-as-not-found).
- Member lacking capability: 403 for management mutations.
- Workspace management pages are viewable for workspace members even without manage capabilities; only mutations are capability-gated.
- All checks are server-side via Policies/Gates, using the canonical capability registry (no raw strings).

**Panel structure (native):**
- Two Filament panels are used:
  - Workspace panel is tenantless and mounted at `/admin`.
  - Tenant panel is tenancy-mounted at `/admin/t/{tenant}`.
- Each resource/page is registered only in the appropriate panel.

**Global search isolation (mandatory):**
- Global search behavior is defined normatively in **FR-080-014**; this section captures intent only (no cross-scope discovery leaks).

**Route removal (dev-stage requirement):**
- Tenant-scoped management routes must not exist after migration (expected 404):
  - `/admin/t/{tenant}/provider-connections*`
  - `/admin/t/{tenant}/required-permissions*`
  - `/admin/t/{tenant}/memberships*`
  - `/admin/t/{tenant}/tenants*` (any management tenancy list/view/edit)

**FR-080-001**: The system MUST expose tenant administration surfaces only under workspace scope (`/admin/tenants*`).
**FR-080-002**: The system MUST expose tenant operations only under tenant scope (`/admin/t/{tenant}/*`).
**FR-080-003**: All workspace management pages MUST require active workspace context + workspace membership; non-member returns 404.
**FR-080-004**: All tenant operational routes MUST require workspace membership + tenant entitlement; non-entitled returns 404.
**FR-080-005**: Management mutations MUST return 403 when capability is missing (in addition to any UI disabling).

**Management surfaces (workspace-scoped):**
**FR-080-006**: Tenants list and tenant manage overview MUST exist under `/admin/tenants` and `/admin/tenants/{tenant}`.
**FR-080-007**: Provider Connections CRUD MUST exist only under `/admin/tenants/{tenant}/provider-connections*`.
**FR-080-008**: Required Permissions remediation UI MUST exist only under `/admin/tenants/{tenant}/required-permissions` and render DB-only.
**FR-080-009**: Tenant Memberships/Roles management MUST exist only under `/admin/tenants/{tenant}/memberships` and audit changes.
**FR-080-010**: Activation/onboarding controls MUST live under workspace-managed tenant pages/wizard entry (not in tenant scope).

**Navigation (enterprise):**
**FR-080-011**: Workspace navigation MUST include Tenants (manage) and Monitoring (Operations, Alerts, Audit Log).
**FR-080-012**: Tenant navigation MUST include only operational modules; no tenant CRUD entry appears in tenant sidebar.
**FR-080-014**: Workspace panel global search MAY return managed tenants only when the user can access them; tenant panel global search MUST NOT include tenant-management resources.
**FR-080-015**: Workspace-managed tenant management routes MUST be reachable under `/admin/tenants*` with a selected workspace context; `/admin/w/{workspace}` is not the canonical management route shape for this feature.

**Filament constraint (hard rule):** any globally searchable Resource MUST have an Edit or View page; otherwise global search will return no results.

**Observability & Audit (minimal, mandatory):**
**FR-080-013**: The system MUST emit audit events for management mutations (tenant changes, role changes, provider connection CRUD/default selection, activation/onboarding state changes) with redacted fields only.

### Key Entities *(include if feature involves data)*

- **Workspace**: Portfolio/customer context; primary security boundary for `/admin/*`.
- **ManagedTenant**: Workspace-owned representation of an Entra/Intune tenant (identified by Entra tenant GUID).
- **TenantMembership**: Assignment of a user to a managed tenant with a role (Owner/Manager/Operator/Readonly).
- **ProviderConnection**: Stored connection/config for provider integration, scoped to a managed tenant.
- **Capability**: Canonical capability registry entries used for authorization decisions.
- **AuditLog entry**: Append-only event record for management mutations (redacted).

## Success Criteria *(mandatory)*

### Measurable Outcomes

- **SC-080-001**: Tenant management surfaces are reachable only under `/admin/tenants*`.
- **SC-080-002**: Tenant operational surfaces are reachable only under `/admin/t/{tenant}/*`.
- **SC-080-003**: Tenant-scoped management routes are not registered and return 404.
- **SC-080-004**: Authorization semantics are consistent: non-member 404, missing capability yields 403 on mutations.
- **SC-080-005**: Internal CTAs/links to manage provider connections/required permissions point to workspace-managed routes.
