TenantAtlas/specs/077-workspace-nav-monitoring-hub/plan.md
Ahmed Darrazi a23684a852 feat(spec-077): global mode + context bar redundancy cleanup
- Define Global Mode: /admin/workspaces is workspace-optional; allowlist in EnsureWorkspaceSelected

- Remove redundancy: no sidebar Switch workspace; no topbar Manage workspaces link; tenant context read-only on /admin/t/{tenant}

- Unify workspace creation auth via WorkspacePolicy + Gate enforcement

- Tests: vendor/bin/sail artisan test --compact tests/Feature/Workspaces tests/Feature/Monitoring tests/Feature/OpsUx tests/Feature/Filament/WorkspaceContextTopbarAndTenantSelectionTest.php
2026-02-06 23:11:14 +01:00

216 lines
9.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.

# Implementation Plan: Workspace-first Navigation & Monitoring Hub
**Branch**: `077-workspace-nav-monitoring-hub` | **Date**: 2026-02-06 | **Spec**: [specs/077-workspace-nav-monitoring-hub/spec.md](spec.md)
**Input**: Feature specification from [specs/077-workspace-nav-monitoring-hub/spec.md](spec.md)
**Note**: This template is filled in by the `/speckit.plan` command. See `.specify/scripts/` for helper scripts.
## Summary
Resolve workspace navigation ambiguity and formalize a workspace-first context model:
- Unambiguous labels: **Switch workspace** (`/admin/choose-workspace`) vs **Manage workspaces** (`/admin/workspaces`).
- Monitoring → **Operations** remains canonical and tenantless (`/admin/operations`, `/admin/operations/{run}`).
- Tenant context influences Operations only via **server-side default filter state** (removable), never via routing.
- Strict non-leaking security semantics:
- Non-member workspace scope → 404 (deny-as-not-found)
- Workspace member missing capability (protected actions/screens) → 403
- Accessing a workspace record outside membership → 404 (deny-as-not-found)
Supporting artifacts:
- [research.md](research.md)
- [data-model.md](data-model.md)
- [contracts/routes.md](contracts/routes.md)
- [quickstart.md](quickstart.md)
## Technical Context
**Language/Version**: PHP 8.4.x
**Primary Dependencies**: Laravel 12, Filament v5, Livewire v4
**Storage**: PostgreSQL (Sail)
**Testing**: Pest v4
**Target Platform**: Web (Filament admin panels)
**Project Type**: Laravel monolith
**Performance Goals**: Operations pages remain DB-only at render; list/detail stay fast on large run tables (pagination + indexed filters)
**Constraints**: Filament-native patterns only; canonical URLs must not depend on tenant context; strict 404/403 non-leakage semantics
**Scale/Scope**: Multi-workspace MSP use; many tenants and many operation runs
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
- Inventory-first: N/A (no inventory semantics changes)
- Read/write separation: PASS (no write operations introduced)
- Graph contract path: N/A (no Graph calls)
- Deterministic capabilities: PASS (capability gating uses existing resolver/registry patterns)
- RBAC-UX: PASS (explicit 404 vs 403 rules)
- RBAC-UX destructive confirmation: N/A (no destructive actions introduced)
- RBAC-UX global search: N/A (no new searchable resources; no changes to global search)
- Tenant isolation: PASS (workspace membership is isolation boundary; tenant context auto-cleared when invalid)
- Run observability: N/A (no new operations/jobs)
- Automation: N/A
- Data minimization: N/A
- Badge semantics (BADGE-001): N/A
## Project Structure
### Documentation (this feature)
```text
specs/077-workspace-nav-monitoring-hub/
├── spec.md
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│ └── routes.md
└── checklists/
└── requirements.md
```
### Source Code (repository root)
```text
app/
├── Filament/
│ ├── Pages/
│ │ └── ChooseWorkspace.php
│ └── Resources/
│ ├── OperationRunResource.php
│ └── OperationRunResource/
│ └── Pages/
│ └── ListOperationRuns.php
├── Http/
│ └── Middleware/
│ └── EnsureWorkspaceSelected.php
├── Providers/
│ └── Filament/
│ └── AdminPanelProvider.php
└── Support/
└── Middleware/
└── EnsureFilamentTenantSelected.php
resources/
└── views/
└── filament/
└── partials/
└── workspace-switcher.blade.php
routes/
└── web.php
tests/
└── Feature/
└── (new tests for navigation labels + 404/403 + operations default filter)
```
**Structure Decision**: Laravel monolith using Filament resources/pages and Laravel middleware.
## Complexity Tracking
No constitution violations.
## Phase 0 — Outline & Research (complete)
All unknowns/decisions have been resolved and recorded:
- Repo reality + ambiguity sources + decisions D1D4: [research.md](research.md)
- No remaining NEEDS CLARIFICATION items in the spec.
## Phase 1 — Design & Contracts (complete)
- Data model: no new tables/columns required; behavior is implemented via middleware + Filament config: [data-model.md](data-model.md)
- Route/security contracts: [contracts/routes.md](contracts/routes.md)
- Manual validation steps + suggested test filters: [quickstart.md](quickstart.md)
## Phase 2 — Implementation Plan (ready for tasks)
### Step 1 — Navigation labels: “one label, one meaning”
- Update admin navigation to include:
- **Switch workspace** (topbar context switcher) → `/admin/choose-workspace`
- **Manage workspaces** (sidebar Settings) → `/admin/workspaces`
- Remove/replace any navigation items labeled only “Workspaces”.
Implementation targets:
- Update [app/Support/Middleware/EnsureFilamentTenantSelected.php](../../app/Support/Middleware/EnsureFilamentTenantSelected.php) navigation builder:
- Change the label from `Workspaces` to `Switch workspace` for the choose-workspace link.
- Ensure this fallback navigation does not accidentally imply CRUD management.
- Update [app/Providers/Filament/AdminPanelProvider.php](../../app/Providers/Filament/AdminPanelProvider.php) nav item label for workspace CRUD to `Manage workspaces`.
- Update [resources/views/filament/partials/workspace-switcher.blade.php](../../resources/views/filament/partials/workspace-switcher.blade.php) text/links to consistently say “Switch workspace”.
- Add reserved Monitoring navigation surfaces for **Alerts** and **Audit Log** as placeholder pages (non-functional “coming soon”) to satisfy FR-011.
### Step 2 — Enforce workspace-scoped RBAC semantics for `/admin/workspaces`
- `/admin/workspaces` stays tenantless and is **Global Mode** (workspace-optional).
- Enforce strict non-leakage semantics:
- Non-member attempting to access a workspace record → **404** (deny-as-not-found)
- Member missing required capability for protected actions/screens → **403**
Implementation targets:
- Scope the Workspaces query (index) to only workspaces the user is a member of.
- Ensure `WorkspacePolicy` returns 404 semantics for non-members (record access).
- Workspace creation is self-serve (policy-driven). Gate edit/membership-management behind canonical workspace capabilities (no raw strings).
- Hide “Manage workspaces” navigation unless the user can manage something workspace-admin related (capability-based).
### Step 3 — Workspace selection redirect + return-to-intended
Requirement: visiting any workspace-scoped page without a selected workspace MUST redirect to `/admin/choose-workspace` and then return to the originally requested URL.
Implementation targets:
- Update [app/Http/Middleware/EnsureWorkspaceSelected.php](../../app/Http/Middleware/EnsureWorkspaceSelected.php):
- When redirecting to `/admin/choose-workspace`, store the intended URL (path + query) in session.
- Preserve the existing exemptions for auth routes and for `/admin/operations/{run}` and Livewire update referers.
- Update both workspace-selection entrypoints to honor intended URLs:
- [app/Filament/Pages/ChooseWorkspace.php](../../app/Filament/Pages/ChooseWorkspace.php)
- [app/Http/Controllers/SwitchWorkspaceController.php](../../app/Http/Controllers/SwitchWorkspaceController.php)
- After setting the workspace, redirect to the stored intended URL (if present and safe), otherwise keep the existing behavior (onboarding / choose-tenant / tenant dashboard).
### Step 4 — Auto-clear invalid tenant context on workspace change
Requirement: if tenant context is active but does not belong to the current workspace, auto-clear tenant context and continue on tenantless workspace pages.
Implementation targets:
- In [app/Support/Middleware/EnsureFilamentTenantSelected.php](../../app/Support/Middleware/EnsureFilamentTenantSelected.php) (or a dedicated middleware used for tenantless pages):
- Detect a persisted Filament tenant that does not match `WorkspaceContext::currentWorkspaceId()`.
- Clear the persisted Filament tenant context (confirm the correct Filament v5 mechanism during implementation).
### Step 5 — Operations: move tenant scoping from query to removable default filter
Requirement: `/admin/operations` stays canonical; if tenant context is active, default to that tenant using server-side default filter state with a visible removable chip.
Implementation targets:
- Update [app/Filament/Resources/OperationRunResource.php](../../app/Filament/Resources/OperationRunResource.php):
- Remove tenant-context filtering from `getEloquentQuery()`.
- Update [app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php](../../app/Filament/Resources/OperationRunResource/Pages/ListOperationRuns.php):
- Add a tenant filter (select) over available tenants in the current workspace.
- Default the filter state from the current tenant context when valid.
- Ensure the filter chip is visible and can be cleared to view workspace-wide operations.
### Step 6 — Tests (Pest) + formatting
Add/adjust tests to cover the strict semantics:
- Navigation labels: “Switch workspace” vs “Manage workspaces” (no ambiguous “Workspaces”).
- `/admin/workspaces`:
- non-member record access → 404
- member missing capability for a protected action/screen → 403
- EnsureWorkspaceSelected:
- visiting `/admin/operations` without workspace → redirects to choose-workspace
- after selecting workspace → returns to intended URL
- Operations default filter:
- with tenant context active → tenant filter default set
- clearing filter → shows workspace-wide results
Tooling:
- Run `./vendor/bin/sail bin pint --dirty`.
- Run focused tests via `./vendor/bin/sail artisan test --compact --filter=...`.